Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
"use strict";
// Test that pseudoelements are displayed correctly in the rule view
const TEST_URI = URL_ROOT + "doc_pseudoelement.html?#:~:text=fox";
const PSEUDO_PREF = "devtools.inspector.show_pseudo_elements";
add_task(async function () {
await pushPref(PSEUDO_PREF, true);
await pushPref("dom.customHighlightAPI.enabled", true);
await pushPref("dom.text_fragments.enabled", true);
await pushPref("layout.css.modern-range-pseudos.enabled", true);
await addTab(TEST_URI);
const { inspector, view } = await openRuleView();
await testTopLeft(inspector, view);
await testTopRight(inspector, view);
await testBottomRight(inspector, view);
await testBottomLeft(inspector, view);
await testParagraph(inspector, view);
await testBody(inspector, view);
await testList(inspector, view);
await testDialogBackdrop(inspector, view);
await testCustomHighlight(inspector, view);
await testSlider(inspector, view);
await testUrlFragmentTextDirective(inspector, view);
});
async function testTopLeft(inspector, view) {
const id = "#topleft";
const rules = await assertPseudoElementRulesNumbers(id, inspector, view, {
elementRulesNb: 4,
firstLineRulesNb: 2,
firstLetterRulesNb: 1,
selectionRulesNb: 1,
markerRulesNb: 0,
afterRulesNb: 1,
beforeRulesNb: 2,
});
const gutters = assertGutters(view);
info("Make sure that clicking on the twisty hides pseudo elements");
const expander = gutters[0].querySelector(".ruleview-expander");
ok(!view.element.children[1].hidden, "Pseudo Elements are expanded");
expander.click();
ok(
view.element.children[1].hidden,
"Pseudo Elements are collapsed by twisty"
);
expander.click();
ok(!view.element.children[1].hidden, "Pseudo Elements are expanded again");
info(
"Make sure that dblclicking on the header container also toggles " +
"the pseudo elements"
);
EventUtils.synthesizeMouseAtCenter(
gutters[0],
{ clickCount: 2 },
view.styleWindow
);
ok(
view.element.children[1].hidden,
"Pseudo Elements are collapsed by dblclicking"
);
const elementRuleView = getRuleViewRuleEditor(view, 3);
const elementFirstLineRule = rules.firstLineRules[0];
const elementFirstLineRuleView = [
...view.element.children[1].children,
].filter(e => {
return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule;
})[0]._ruleEditor;
is(
convertTextPropsToString(elementFirstLineRule.textProps),
"color: orange",
"TopLeft firstLine properties are correct"
);
let onAdded = view.once("ruleview-changed");
let firstProp = elementFirstLineRuleView.addProperty(
"background-color",
"rgb(0, 255, 0)",
"",
true
);
await onAdded;
onAdded = view.once("ruleview-changed");
const secondProp = elementFirstLineRuleView.addProperty(
"font-style",
"italic",
"",
true
);
await onAdded;
is(
firstProp,
elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 2],
"First added property is on back of array"
);
is(
secondProp,
elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 1],
"Second added property is on back of array"
);
is(
await getComputedStyleProperty(id, ":first-line", "background-color"),
"rgb(0, 255, 0)",
"Added property should have been used."
);
is(
await getComputedStyleProperty(id, ":first-line", "font-style"),
"italic",
"Added property should have been used."
);
is(
await getComputedStyleProperty(id, null, "text-decoration-line"),
"none",
"Added property should not apply to element"
);
await togglePropStatus(view, firstProp);
is(
await getComputedStyleProperty(id, ":first-line", "background-color"),
"rgb(255, 0, 0)",
"Disabled property should now have been used."
);
is(
await getComputedStyleProperty(id, null, "background-color"),
"rgb(221, 221, 221)",
"Added property should not apply to element"
);
await togglePropStatus(view, firstProp);
is(
await getComputedStyleProperty(id, ":first-line", "background-color"),
"rgb(0, 255, 0)",
"Added property should have been used."
);
is(
await getComputedStyleProperty(id, null, "text-decoration-line"),
"none",
"Added property should not apply to element"
);
onAdded = view.once("ruleview-changed");
firstProp = elementRuleView.addProperty(
"background-color",
"rgb(0, 0, 255)",
"",
true
);
await onAdded;
is(
await getComputedStyleProperty(id, null, "background-color"),
"rgb(0, 0, 255)",
"Added property should have been used."
);
is(
await getComputedStyleProperty(id, ":first-line", "background-color"),
"rgb(0, 255, 0)",
"Added prop does not apply to pseudo"
);
}
async function testTopRight(inspector, view) {
await assertPseudoElementRulesNumbers("#topright", inspector, view, {
elementRulesNb: 4,
firstLineRulesNb: 1,
firstLetterRulesNb: 1,
selectionRulesNb: 0,
markerRulesNb: 0,
beforeRulesNb: 2,
afterRulesNb: 1,
});
const gutters = assertGutters(view);
const expander = gutters[0].querySelector(".ruleview-expander");
ok(
!view.element.firstChild.classList.contains("show-expandable-container"),
"Pseudo Elements remain collapsed after switching element"
);
expander.scrollIntoView();
expander.click();
ok(
!view.element.children[1].hidden,
"Pseudo Elements are shown again after clicking twisty"
);
}
async function testBottomRight(inspector, view) {
await assertPseudoElementRulesNumbers("#bottomright", inspector, view, {
elementRulesNb: 4,
firstLineRulesNb: 1,
firstLetterRulesNb: 1,
selectionRulesNb: 0,
markerRulesNb: 0,
beforeRulesNb: 3,
afterRulesNb: 1,
});
}
async function testBottomLeft(inspector, view) {
await assertPseudoElementRulesNumbers("#bottomleft", inspector, view, {
elementRulesNb: 4,
firstLineRulesNb: 1,
firstLetterRulesNb: 1,
selectionRulesNb: 0,
markerRulesNb: 0,
beforeRulesNb: 2,
afterRulesNb: 1,
});
}
async function testParagraph(inspector, view) {
const rules = await assertPseudoElementRulesNumbers(
"#bottomleft p",
inspector,
view,
{
elementRulesNb: 3,
firstLineRulesNb: 1,
firstLetterRulesNb: 1,
selectionRulesNb: 2,
markerRulesNb: 0,
beforeRulesNb: 0,
afterRulesNb: 0,
}
);
assertGutters(view);
const elementFirstLineRule = rules.firstLineRules[0];
is(
convertTextPropsToString(elementFirstLineRule.textProps),
"background: blue",
"Paragraph first-line properties are correct"
);
const elementFirstLetterRule = rules.firstLetterRules[0];
is(
convertTextPropsToString(elementFirstLetterRule.textProps),
"color: red; font-size: 130%",
"Paragraph first-letter properties are correct"
);
const elementSelectionRule = rules.selectionRules[0];
is(
convertTextPropsToString(elementSelectionRule.textProps),
"color: white; background: black",
"Paragraph first-letter properties are correct"
);
}
async function testBody(inspector, view) {
await testNode("body", inspector, view);
const gutters = getGutters(view);
is(gutters.length, 0, "There are no gutter headings");
}
async function testList(inspector, view) {
await assertPseudoElementRulesNumbers("#list", inspector, view, {
elementRulesNb: 4,
firstLineRulesNb: 1,
firstLetterRulesNb: 1,
selectionRulesNb: 0,
markerRulesNb: 1,
beforeRulesNb: 1,
afterRulesNb: 1,
});
assertGutters(view);
}
async function testDialogBackdrop(inspector, view) {
await assertPseudoElementRulesNumbers("dialog", inspector, view, {
elementRulesNb: 3,
backdropRules: 1,
});
assertGutters(view);
}
async function testCustomHighlight(inspector, view) {
const { highlightRules } = await assertPseudoElementRulesNumbers(
".highlights-container",
inspector,
view,
{
elementRulesNb: 4,
highlightRulesNb: 3,
}
);
is(
highlightRules[0].pseudoElement,
"::highlight(filter)",
"First highlight rule is for the filter highlight"
);
is(
highlightRules[1].pseudoElement,
"::highlight(search)",
"Second highlight rule is for the search highlight"
);
is(
highlightRules[2].pseudoElement,
"::highlight(search)",
"Third highlight rule is also for the search highlight"
);
is(highlightRules.length, 3, "Got all 3 active rules, but not unused one");
// Check that properties are marked as overridden only when they're on the same Highlight
is(
convertTextPropsToString(highlightRules[0].textProps),
`background-color: purple`,
"Got expected properties for filter highlight"
);
is(
convertTextPropsToString(highlightRules[1].textProps),
`color: white`,
"Got expected properties for first search highlight"
);
is(
convertTextPropsToString(highlightRules[2].textProps),
`background-color: tomato; ~~color: gold~~`,
"Got expected properties for second search highlight, `color` is marked as overridden"
);
assertGutters(view);
}
async function testSlider(inspector, view) {
await assertPseudoElementRulesNumbers(
"input[type=range].slider",
inspector,
view,
{
elementRulesNb: 3,
sliderFillRulesNb: 1,
sliderThumbRulesNb: 1,
sliderTrackRulesNb: 1,
}
);
assertGutters(view);
info(
"Check that ::slider-* pseudo elements are not displayed for non-range inputs"
);
await assertPseudoElementRulesNumbers(
"input[type=text].slider",
inspector,
view,
{
elementRulesNb: 3,
sliderFillRulesNb: 0,
sliderThumbRulesNb: 0,
sliderTrackRulesNb: 0,
}
);
}
async function testUrlFragmentTextDirective(inspector, view) {
await assertPseudoElementRulesNumbers(
".url-fragment-text-directives",
inspector,
view,
{
elementRulesNb: 3,
targetTextRulesNb: 1,
}
);
assertGutters(view);
}
function convertTextPropsToString(textProps) {
return textProps
.map(
t =>
`${t.overridden ? "~~" : ""}${t.name}: ${t.value}${
t.overridden ? "~~" : ""
}`
)
.join("; ");
}
async function testNode(selector, inspector, view) {
await selectNode(selector, inspector);
const elementStyle = view._elementStyle;
return elementStyle;
}
async function assertPseudoElementRulesNumbers(
selector,
inspector,
view,
ruleNbs
) {
const elementStyle = await testNode(selector, inspector, view);
const rules = {
elementRules: elementStyle.rules.filter(rule => !rule.pseudoElement),
firstLineRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::first-line"
),
firstLetterRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::first-letter"
),
selectionRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::selection"
),
markerRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::marker"
),
beforeRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::before"
),
afterRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::after"
),
backdropRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::backdrop"
),
highlightRules: elementStyle.rules.filter(rule =>
rule.pseudoElement?.startsWith("::highlight(")
),
sliderFillRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::slider-fill"
),
sliderThumbRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::slider-thumb"
),
sliderTrackRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::slider-track"
),
targetTextRules: elementStyle.rules.filter(
rule => rule.pseudoElement === "::target-text"
),
};
is(
rules.elementRules.length,
ruleNbs.elementRulesNb || 0,
selector + " has the correct number of non pseudo element rules"
);
is(
rules.firstLineRules.length,
ruleNbs.firstLineRulesNb || 0,
selector + " has the correct number of ::first-line rules"
);
is(
rules.firstLetterRules.length,
ruleNbs.firstLetterRulesNb || 0,
selector + " has the correct number of ::first-letter rules"
);
is(
rules.selectionRules.length,
ruleNbs.selectionRulesNb || 0,
selector + " has the correct number of ::selection rules"
);
is(
rules.markerRules.length,
ruleNbs.markerRulesNb || 0,
selector + " has the correct number of ::marker rules"
);
is(
rules.beforeRules.length,
ruleNbs.beforeRulesNb || 0,
selector + " has the correct number of ::before rules"
);
is(
rules.afterRules.length,
ruleNbs.afterRulesNb || 0,
selector + " has the correct number of ::after rules"
);
is(
rules.highlightRules.length,
ruleNbs.highlightRulesNb || 0,
selector + " has the correct number of ::highlight rules"
);
is(
rules.sliderFillRules.length,
ruleNbs.sliderFillRulesNb || 0,
selector + " has the correct number of ::slider-fill rules"
);
is(
rules.sliderThumbRules.length,
ruleNbs.sliderThumbRulesNb || 0,
selector + " has the correct number of ::slider-thumb rules"
);
is(
rules.sliderTrackRules.length,
ruleNbs.sliderTrackRulesNb || 0,
selector + " has the correct number of ::slider-track rules"
);
is(
rules.targetTextRules.length,
ruleNbs.targetTextRulesNb || 0,
selector + " has the correct number of ::target-text rules"
);
// If we do have pseudo element rules displayed, ensure we don't mark their selectors
// as matched or unmatched
if (
rules.elementRules.length &&
elementStyle.rules.length !== rules.elementRules.length
) {
const pseudoElementContainer = view.styleWindow.document.getElementById(
"pseudo-elements-container"
);
const selectors = Array.from(
pseudoElementContainer.querySelectorAll(".ruleview-selector")
);
ok(selectors.length, "We do have selectors for pseudo element rules");
ok(
selectors.every(
selectorEl =>
!selectorEl.classList.contains("matched") &&
!selectorEl.classList.contains("unmatched")
),
"Pseudo element selectors are not marked as matched nor unmatched"
);
}
return rules;
}
function getGutters(view) {
return view.element.querySelectorAll(".ruleview-header");
}
function assertGutters(view) {
const gutters = getGutters(view);
is(gutters.length, 3, "There are 3 gutter headings");
is(gutters[0].textContent, "Pseudo-elements", "Gutter heading is correct");
is(gutters[1].textContent, "This Element", "Gutter heading is correct");
is(
gutters[2].textContent,
"Inherited from body",
"Gutter heading is correct"
);
return gutters;
}