mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Bug 1788033 - [devtools] Display container, layer and supports in StyleEditor at-rules sidebar. r=jdescottes.
Differential Revision: https://phabricator.services.mozilla.com/D157084
This commit is contained in:
parent
38d79b9eaf
commit
6153f85643
@ -1329,31 +1329,42 @@ export class StyleEditorUI extends EventEmitter {
|
||||
inSource = true;
|
||||
|
||||
const div = this.#panelDoc.createElementNS(HTML_NS, "div");
|
||||
div.className = "at-rule-label";
|
||||
div.classList.add("at-rule-label", rule.type);
|
||||
div.addEventListener(
|
||||
"click",
|
||||
this.#jumpToLocation.bind(this, location)
|
||||
);
|
||||
|
||||
const cond = this.#panelDoc.createElementNS(HTML_NS, "div");
|
||||
const ruleTextContainer = this.#panelDoc.createElementNS(
|
||||
HTML_NS,
|
||||
"div"
|
||||
);
|
||||
const type = this.#panelDoc.createElementNS(HTML_NS, "span");
|
||||
type.className = "at-rule-type";
|
||||
type.append(this.#panelDoc.createTextNode(`@${rule.type}\u00A0`));
|
||||
if (rule.type == "layer" && rule.layerName) {
|
||||
type.append(this.#panelDoc.createTextNode(`${rule.layerName}\u00A0`));
|
||||
}
|
||||
|
||||
const cond = this.#panelDoc.createElementNS(HTML_NS, "span");
|
||||
cond.className = "at-rule-condition";
|
||||
if (!rule.matches) {
|
||||
if (rule.type == "media" && !rule.matches) {
|
||||
cond.classList.add("media-condition-unmatched");
|
||||
}
|
||||
if (this.#commands.descriptorFront.isLocalTab) {
|
||||
this.#setConditionContents(cond, rule.conditionText);
|
||||
this.#setConditionContents(cond, rule.conditionText, rule.type);
|
||||
} else {
|
||||
cond.textContent = rule.conditionText;
|
||||
}
|
||||
div.appendChild(cond);
|
||||
|
||||
const link = this.#panelDoc.createElementNS(HTML_NS, "div");
|
||||
link.className = "at-rule-line theme-link";
|
||||
if (location.line != -1) {
|
||||
link.textContent = ":" + location.line;
|
||||
}
|
||||
div.appendChild(link);
|
||||
|
||||
ruleTextContainer.append(type, cond);
|
||||
div.append(ruleTextContainer, link);
|
||||
list.appendChild(div);
|
||||
}
|
||||
|
||||
@ -1366,38 +1377,55 @@ export class StyleEditorUI extends EventEmitter {
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to safely inject media query links
|
||||
* Set the condition text for the at-rule element.
|
||||
* For media queries, it also injects links to open RDM at a specific size.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* The element corresponding to the media sidebar condition
|
||||
* @param {String} rawText
|
||||
* The raw condition text to parse
|
||||
* @param {String} ruleConditionText
|
||||
* The rule conditionText
|
||||
* @param {String} type
|
||||
* The type of the at-rule (e.g. "media", "layer", "supports", …)
|
||||
*/
|
||||
#setConditionContents(element, rawText) {
|
||||
#setConditionContents(element, ruleConditionText, type) {
|
||||
if (!ruleConditionText) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For non-media rules, we don't do anything more than displaying the conditionText
|
||||
// as there are no other condition text that would justify opening RDM at a specific
|
||||
// size (e.g. `@container` condition is relative to a container size, which varies
|
||||
// depending the node the rule applies to).
|
||||
if (type !== "media") {
|
||||
const node = this.#panelDoc.createTextNode(ruleConditionText);
|
||||
element.appendChild(node);
|
||||
return;
|
||||
}
|
||||
|
||||
const minMaxPattern = /(min\-|max\-)(width|height):\s\d+(px)/gi;
|
||||
|
||||
let match = minMaxPattern.exec(rawText);
|
||||
let match = minMaxPattern.exec(ruleConditionText);
|
||||
let lastParsed = 0;
|
||||
while (match && match.index != minMaxPattern.lastIndex) {
|
||||
const matchEnd = match.index + match[0].length;
|
||||
const node = this.#panelDoc.createTextNode(
|
||||
rawText.substring(lastParsed, match.index)
|
||||
ruleConditionText.substring(lastParsed, match.index)
|
||||
);
|
||||
element.appendChild(node);
|
||||
|
||||
const link = this.#panelDoc.createElementNS(HTML_NS, "a");
|
||||
link.href = "#";
|
||||
link.className = "media-responsive-mode-toggle";
|
||||
link.textContent = rawText.substring(match.index, matchEnd);
|
||||
link.textContent = ruleConditionText.substring(match.index, matchEnd);
|
||||
link.addEventListener("click", this.#onMediaConditionClick.bind(this));
|
||||
element.appendChild(link);
|
||||
|
||||
match = minMaxPattern.exec(rawText);
|
||||
match = minMaxPattern.exec(ruleConditionText);
|
||||
lastParsed = matchEnd;
|
||||
}
|
||||
|
||||
const node = this.#panelDoc.createTextNode(
|
||||
rawText.substring(lastParsed, rawText.length)
|
||||
ruleConditionText.substring(lastParsed, ruleConditionText.length)
|
||||
);
|
||||
element.appendChild(node);
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ support-files =
|
||||
!/devtools/client/shared/test/highlighter-test-actor.js
|
||||
|
||||
[browser_styleeditor_add_stylesheet.js]
|
||||
[browser_styleeditor_at_rules_sidebar.js]
|
||||
[browser_styleeditor_autocomplete.js]
|
||||
[browser_styleeditor_autocomplete-disabled.js]
|
||||
[browser_styleeditor_bom.js]
|
||||
@ -101,7 +102,6 @@ skip-if = !debug && (os == "win") || (os == "linux" && os_version == "18.04") #b
|
||||
[browser_styleeditor_inline_friendly_names.js]
|
||||
[browser_styleeditor_loading.js]
|
||||
[browser_styleeditor_loading_with_containers.js]
|
||||
[browser_styleeditor_media_sidebar.js]
|
||||
[browser_styleeditor_media_sidebar_links.js]
|
||||
[browser_styleeditor_media_sidebar_sourcemaps.js]
|
||||
[browser_styleeditor_missing_stylesheet.js]
|
||||
|
@ -34,6 +34,8 @@ const NEW_RULE = `
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("layout.css.container-queries.enabled", true);
|
||||
|
||||
const { ui } = await openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
is(ui.editors.length, 4, "correct number of editors");
|
||||
@ -82,7 +84,7 @@ async function testInlineMediaEditor(ui, editor) {
|
||||
is(sidebar.hidden, false, "sidebar is showing on editor with @media");
|
||||
|
||||
const entries = sidebar.querySelectorAll(".at-rule-label");
|
||||
is(entries.length, 2, "2 @media rules displayed in sidebar");
|
||||
is(entries.length, 5, "5 @media rules displayed in sidebar");
|
||||
|
||||
await testRule({
|
||||
ui,
|
||||
@ -91,15 +93,45 @@ async function testInlineMediaEditor(ui, editor) {
|
||||
conditionText: "screen",
|
||||
matches: true,
|
||||
line: 2,
|
||||
type: "media",
|
||||
});
|
||||
|
||||
await testRule({
|
||||
ui,
|
||||
editor,
|
||||
rule: entries[1],
|
||||
conditionText: "(display: flex)",
|
||||
line: 7,
|
||||
type: "support",
|
||||
});
|
||||
|
||||
await testRule({
|
||||
ui,
|
||||
editor,
|
||||
rule: entries[2],
|
||||
conditionText: "(1px < height < 10000px)",
|
||||
matches: true,
|
||||
line: 8,
|
||||
type: "media",
|
||||
});
|
||||
|
||||
await testRule({
|
||||
ui,
|
||||
editor,
|
||||
rule: entries[3],
|
||||
conditionText: "",
|
||||
line: 16,
|
||||
type: "layer",
|
||||
layerName: "myLayer",
|
||||
});
|
||||
|
||||
await testRule({
|
||||
ui,
|
||||
editor,
|
||||
rule: entries[4],
|
||||
conditionText: "(min-width: 1px)",
|
||||
line: 17,
|
||||
type: "container",
|
||||
});
|
||||
}
|
||||
|
||||
@ -215,24 +247,45 @@ async function testMediaRuleAdded(ui, editor) {
|
||||
* @param {Element} options.rule: The rule element in the media sidebar
|
||||
* @param {String} options.conditionText: media query condition text
|
||||
* @param {Boolean} options.matches: Whether or not the document matches the rule
|
||||
* @param {String} options.layerName: Optional name of the @layer
|
||||
* @param {Number} options.line: Line of the rule
|
||||
* @param {String} options.type: The type of the rule (container, layer, media, support ).
|
||||
* Defaults to "media".
|
||||
*/
|
||||
async function testRule({ ui, editor, rule, conditionText, matches, line }) {
|
||||
async function testRule({
|
||||
ui,
|
||||
editor,
|
||||
rule,
|
||||
conditionText,
|
||||
matches,
|
||||
layerName,
|
||||
line,
|
||||
type = "media",
|
||||
}) {
|
||||
const atTypeEl = rule.querySelector(".at-rule-type");
|
||||
is(
|
||||
atTypeEl.textContent,
|
||||
`@${type}\u00A0${layerName ? `${layerName}\u00A0` : ""}`,
|
||||
"label for at-rule type is correct"
|
||||
);
|
||||
|
||||
const cond = rule.querySelector(".at-rule-condition");
|
||||
is(
|
||||
cond.textContent,
|
||||
conditionText,
|
||||
"media label is correct for " + conditionText
|
||||
"condition label is correct for " + conditionText
|
||||
);
|
||||
|
||||
const matched = !cond.classList.contains("media-condition-unmatched");
|
||||
ok(
|
||||
matches ? matched : !matched,
|
||||
"media rule is " + (matches ? "matched" : "unmatched")
|
||||
);
|
||||
if (type == "media") {
|
||||
const matched = !cond.classList.contains("media-condition-unmatched");
|
||||
ok(
|
||||
matches ? matched : !matched,
|
||||
"media rule is " + (matches ? "matched" : "unmatched")
|
||||
);
|
||||
}
|
||||
|
||||
const mediaRuleLine = rule.querySelector(".at-rule-line");
|
||||
is(mediaRuleLine.textContent, ":" + line, "correct line number shown");
|
||||
const ruleLine = rule.querySelector(".at-rule-line");
|
||||
is(ruleLine.textContent, ":" + line, "correct line number shown");
|
||||
|
||||
info(
|
||||
"Check that clicking on the rule jumps to the expected position in the stylesheet"
|
@ -25,12 +25,12 @@ add_task(async function() {
|
||||
// Test editor with @media rules
|
||||
const mediaEditor = ui.editors[0];
|
||||
await openEditor(mediaEditor);
|
||||
testMediaEditor(mediaEditor);
|
||||
testAtRulesEditor(mediaEditor);
|
||||
|
||||
Services.prefs.clearUserPref(MAP_PREF);
|
||||
});
|
||||
|
||||
function testMediaEditor(editor) {
|
||||
function testAtRulesEditor(editor) {
|
||||
const sidebar = editor.details.querySelector(".stylesheet-sidebar");
|
||||
is(sidebar.hidden, false, "sidebar is showing on editor with @media");
|
||||
|
||||
|
@ -24,6 +24,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer myLayer {}
|
||||
@container (min-width: 1px) {}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -132,10 +132,17 @@ li.error > .stylesheet-info > .stylesheet-more > .stylesheet-error-message {
|
||||
}
|
||||
|
||||
.at-rule-label {
|
||||
display: flex;
|
||||
display: grid;
|
||||
/*
|
||||
* +----------------------------------------------+
|
||||
* | Rule information | line number |
|
||||
* +----------------------------------------------+
|
||||
*/
|
||||
grid-template-columns: 1fr max-content;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
cursor: pointer;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.media-responsive-mode-toggle {
|
||||
@ -151,11 +158,6 @@ li.error > .stylesheet-info > .stylesheet-more > .stylesheet-error-message {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.at-rule-condition {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stylesheet-toggle {
|
||||
display: -moz-box;
|
||||
cursor: pointer;
|
||||
|
@ -597,8 +597,55 @@ class StyleSheetsManager extends EventEmitter {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rule.type === CSSRule.MEDIA_RULE) {
|
||||
rules.push(rule);
|
||||
const line = InspectorUtils.getRelativeRuleLine(rule);
|
||||
const column = InspectorUtils.getRuleColumn(rule);
|
||||
|
||||
const className = ChromeUtils.getClassName(rule);
|
||||
if (className === "CSSMediaRule") {
|
||||
let matches = false;
|
||||
|
||||
try {
|
||||
const mql = win.matchMedia(rule.media.mediaText);
|
||||
matches = mql.matches;
|
||||
mql.onchange = this._onMatchesChange.bind(
|
||||
this,
|
||||
resourceId,
|
||||
rules.length
|
||||
);
|
||||
this._mqlList.push(mql);
|
||||
} catch (e) {
|
||||
// Ignored
|
||||
}
|
||||
|
||||
rules.push({
|
||||
type: "media",
|
||||
mediaText: rule.media.mediaText,
|
||||
conditionText: rule.conditionText,
|
||||
matches,
|
||||
line,
|
||||
column,
|
||||
});
|
||||
} else if (className === "CSSContainerRule") {
|
||||
rules.push({
|
||||
type: "container",
|
||||
conditionText: rule.conditionText,
|
||||
line,
|
||||
column,
|
||||
});
|
||||
} else if (className === "CSSSupportsRule") {
|
||||
rules.push({
|
||||
type: "support",
|
||||
conditionText: rule.conditionText,
|
||||
line,
|
||||
column,
|
||||
});
|
||||
} else if (className === "CSSLayerBlockRule") {
|
||||
rules.push({
|
||||
type: "layer",
|
||||
layerName: rule.name,
|
||||
line,
|
||||
column,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.cssRules) {
|
||||
@ -607,27 +654,7 @@ class StyleSheetsManager extends EventEmitter {
|
||||
}
|
||||
};
|
||||
traverseRules(styleSheetRules);
|
||||
|
||||
return rules.map((rule, index) => {
|
||||
let matches = false;
|
||||
|
||||
try {
|
||||
const mql = win.matchMedia(rule.media.mediaText);
|
||||
matches = mql.matches;
|
||||
mql.onchange = this._onMatchesChange.bind(this, resourceId, index);
|
||||
this._mqlList.push(mql);
|
||||
} catch (e) {
|
||||
// Ignored
|
||||
}
|
||||
|
||||
return {
|
||||
mediaText: rule.media.mediaText,
|
||||
conditionText: rule.conditionText,
|
||||
matches,
|
||||
line: InspectorUtils.getRelativeRuleLine(rule),
|
||||
column: InspectorUtils.getRuleColumn(rule),
|
||||
};
|
||||
});
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -637,7 +664,7 @@ class StyleSheetsManager extends EventEmitter {
|
||||
* @param {String} resourceId
|
||||
* The id associated with the stylesheet
|
||||
* @param {Number} index
|
||||
* The index of the media rule relatively to all the other media rules of the stylesheet
|
||||
* The index of the media rule relatively to all the other at-rules of the stylesheet
|
||||
* @param {MediaQueryList} mql
|
||||
* The result of matchMedia for the given media rule
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user