зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1498224 - [devtools] Apply new classnames as you type in the .cls section of the rule-view r=jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D129515
This commit is contained in:
Родитель
cdd275e25c
Коммит
702f0ad086
|
@ -192,6 +192,8 @@ Selection.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.emit("node-front-will-unset");
|
||||||
|
|
||||||
this._isSlotted = isSlotted;
|
this._isSlotted = isSlotted;
|
||||||
this._nodeFront = nodeFront;
|
this._nodeFront = nodeFront;
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ class ClassList {
|
||||||
this.inspector.on("markupmutation", this.onMutations);
|
this.inspector.on("markupmutation", this.onMutations);
|
||||||
|
|
||||||
this.classListProxyNode = this.inspector.panelDoc.createElement("div");
|
this.classListProxyNode = this.inspector.panelDoc.createElement("div");
|
||||||
|
this.previewClasses = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -72,9 +73,11 @@ class ClassList {
|
||||||
if (!CLASSES.has(this.currentNode)) {
|
if (!CLASSES.has(this.currentNode)) {
|
||||||
// Use the proxy node to get a clean list of classes.
|
// Use the proxy node to get a clean list of classes.
|
||||||
this.classListProxyNode.className = this.currentNode.className;
|
this.classListProxyNode.className = this.currentNode.className;
|
||||||
const nodeClasses = [
|
const nodeClasses = [...new Set([...this.classListProxyNode.classList])]
|
||||||
...new Set([...this.classListProxyNode.classList]),
|
.filter(
|
||||||
].map(name => {
|
className => !this.previewClasses.split(" ").includes(className)
|
||||||
|
)
|
||||||
|
.map(name => {
|
||||||
return { name, isApplied: true };
|
return { name, isApplied: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,10 +92,15 @@ class ClassList {
|
||||||
* enabled classes are added.
|
* enabled classes are added.
|
||||||
*/
|
*/
|
||||||
get currentClassesPreview() {
|
get currentClassesPreview() {
|
||||||
return this.currentClasses
|
const currentClasses = this.currentClasses
|
||||||
.filter(({ isApplied }) => isApplied)
|
.filter(({ isApplied }) => isApplied)
|
||||||
.map(({ name }) => name)
|
.map(({ name }) => name);
|
||||||
.join(" ");
|
const previewClasses = this.previewClasses
|
||||||
|
.split(" ")
|
||||||
|
.filter(previewClass => !currentClasses.includes(previewClass))
|
||||||
|
.filter(item => item !== "");
|
||||||
|
|
||||||
|
return currentClasses.concat(previewClasses).join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,6 +129,7 @@ class ClassList {
|
||||||
*/
|
*/
|
||||||
addClassName(classNameString) {
|
addClassName(classNameString) {
|
||||||
this.classListProxyNode.className = classNameString;
|
this.classListProxyNode.className = classNameString;
|
||||||
|
this.eraseClassPreview();
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
[...new Set([...this.classListProxyNode.classList])].map(name => {
|
[...new Set([...this.classListProxyNode.classList])].map(name => {
|
||||||
return this.addClass(name);
|
return this.addClass(name);
|
||||||
|
@ -211,6 +220,17 @@ class ClassList {
|
||||||
this.currentNode
|
this.currentNode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previewClass(inputClasses) {
|
||||||
|
if (this.previewClasses !== inputClasses) {
|
||||||
|
this.previewClasses = inputClasses;
|
||||||
|
this.applyClassState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eraseClassPreview() {
|
||||||
|
this.previewClass("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ClassList;
|
module.exports = ClassList;
|
||||||
|
|
|
@ -70,7 +70,9 @@ add_task(async function() {
|
||||||
|
|
||||||
info(`Enter the test string in the field: ${textEntered}`);
|
info(`Enter the test string in the field: ${textEntered}`);
|
||||||
for (const key of textEntered.split("")) {
|
for (const key of textEntered.split("")) {
|
||||||
|
const onPreviewMutation = inspector.once("markupmutation");
|
||||||
EventUtils.synthesizeKey(key, {}, view.styleWindow);
|
EventUtils.synthesizeKey(key, {}, view.styleWindow);
|
||||||
|
await onPreviewMutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
info("Submit the change and wait for the textfield to become empty");
|
info("Submit the change and wait for the textfield to become empty");
|
||||||
|
|
|
@ -45,6 +45,7 @@ add_task(async function() {
|
||||||
const { autocompletePopup } = view.classListPreviewer;
|
const { autocompletePopup } = view.classListPreviewer;
|
||||||
let onPopupOpened = autocompletePopup.once("popup-opened");
|
let onPopupOpened = autocompletePopup.once("popup-opened");
|
||||||
EventUtils.synthesizeKey("a", {}, view.styleWindow);
|
EventUtils.synthesizeKey("a", {}, view.styleWindow);
|
||||||
|
await waitForClassApplied("a");
|
||||||
await onPopupOpened;
|
await onPopupOpened;
|
||||||
await checkAutocompleteItems(
|
await checkAutocompleteItems(
|
||||||
autocompletePopup,
|
autocompletePopup,
|
||||||
|
@ -55,17 +56,14 @@ add_task(async function() {
|
||||||
info(
|
info(
|
||||||
"Test that typing more letters filters the autocomplete popup and uses the cache mechanism"
|
"Test that typing more letters filters the autocomplete popup and uses the cache mechanism"
|
||||||
);
|
);
|
||||||
const onCacheHit = inspector.inspectorFront.pageStyle.once(
|
|
||||||
"getAttributesInOwnerDocument-cache-hit"
|
|
||||||
);
|
|
||||||
EventUtils.sendString("uto-b", view.styleWindow);
|
EventUtils.sendString("uto-b", view.styleWindow);
|
||||||
|
await waitForClassApplied("auto-b");
|
||||||
|
|
||||||
await checkAutocompleteItems(
|
await checkAutocompleteItems(
|
||||||
autocompletePopup,
|
autocompletePopup,
|
||||||
allClasses.filter(cls => cls.startsWith("auto-b")),
|
allClasses.filter(cls => cls.startsWith("auto-b")),
|
||||||
"The autocomplete popup was filtered with the content of the input"
|
"The autocomplete popup was filtered with the content of the input"
|
||||||
);
|
);
|
||||||
await onCacheHit;
|
|
||||||
ok(true, "The results were retrieved from the cache mechanism");
|
ok(true, "The results were retrieved from the cache mechanism");
|
||||||
|
|
||||||
info("Test that autocomplete shows up-to-date results");
|
info("Test that autocomplete shows up-to-date results");
|
||||||
|
@ -76,10 +74,12 @@ add_task(async function() {
|
||||||
content.document.body.classList.add("auto-body-added-by-script");
|
content.document.body.classList.add("auto-body-added-by-script");
|
||||||
});
|
});
|
||||||
await onNewMutation;
|
await onNewMutation;
|
||||||
|
await waitForClassApplied("auto-body-added-by-script");
|
||||||
|
|
||||||
// input is now auto-body
|
// input is now auto-body
|
||||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||||
EventUtils.sendString("ody", view.styleWindow);
|
EventUtils.sendString("ody", view.styleWindow);
|
||||||
|
await waitForClassApplied("auto-body");
|
||||||
await onPopupOpened;
|
await onPopupOpened;
|
||||||
await checkAutocompleteItems(
|
await checkAutocompleteItems(
|
||||||
autocompletePopup,
|
autocompletePopup,
|
||||||
|
@ -96,6 +96,7 @@ add_task(async function() {
|
||||||
// input is now auto-bodyy
|
// input is now auto-bodyy
|
||||||
let onPopupClosed = autocompletePopup.once("popup-closed");
|
let onPopupClosed = autocompletePopup.once("popup-closed");
|
||||||
EventUtils.synthesizeKey("y", {}, view.styleWindow);
|
EventUtils.synthesizeKey("y", {}, view.styleWindow);
|
||||||
|
await waitForClassApplied("auto-bodyy");
|
||||||
await onPopupClosed;
|
await onPopupClosed;
|
||||||
ok(true, "The popup was closed as expected");
|
ok(true, "The popup was closed as expected");
|
||||||
await checkAutocompleteItems(autocompletePopup, [], "The popup was cleared");
|
await checkAutocompleteItems(autocompletePopup, [], "The popup was cleared");
|
||||||
|
@ -108,6 +109,7 @@ add_task(async function() {
|
||||||
|
|
||||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||||
EventUtils.synthesizeKey("a", {}, view.styleWindow);
|
EventUtils.synthesizeKey("a", {}, view.styleWindow);
|
||||||
|
await waitForClassApplied("a");
|
||||||
await onPopupOpened;
|
await onPopupOpened;
|
||||||
|
|
||||||
await checkAutocompleteItems(
|
await checkAutocompleteItems(
|
||||||
|
@ -147,6 +149,7 @@ add_task(async function() {
|
||||||
|
|
||||||
onPopupClosed = autocompletePopup.once("popup-closed");
|
onPopupClosed = autocompletePopup.once("popup-closed");
|
||||||
EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow);
|
EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow);
|
||||||
|
await waitForClassApplied("auto-body-added-by-script");
|
||||||
await onPopupClosed;
|
await onPopupClosed;
|
||||||
is(
|
is(
|
||||||
textInput.value,
|
textInput.value,
|
||||||
|
@ -157,6 +160,7 @@ add_task(async function() {
|
||||||
// Backspace to show the list again
|
// Backspace to show the list again
|
||||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||||
EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
|
EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
|
||||||
|
await waitForClassApplied("auto-body-added-by-scrip");
|
||||||
await onPopupOpened;
|
await onPopupOpened;
|
||||||
is(
|
is(
|
||||||
textInput.value,
|
textInput.value,
|
||||||
|
@ -172,6 +176,7 @@ add_task(async function() {
|
||||||
// Enter to accept
|
// Enter to accept
|
||||||
onPopupClosed = autocompletePopup.once("popup-closed");
|
onPopupClosed = autocompletePopup.once("popup-closed");
|
||||||
EventUtils.synthesizeKey("KEY_Enter", {}, view.styleWindow);
|
EventUtils.synthesizeKey("KEY_Enter", {}, view.styleWindow);
|
||||||
|
await waitForClassRemoved("auto-body-added-by-scrip");
|
||||||
await onPopupClosed;
|
await onPopupClosed;
|
||||||
is(
|
is(
|
||||||
textInput.value,
|
textInput.value,
|
||||||
|
@ -182,6 +187,7 @@ add_task(async function() {
|
||||||
// Backspace to show again
|
// Backspace to show again
|
||||||
onPopupOpened = autocompletePopup.once("popup-opened");
|
onPopupOpened = autocompletePopup.once("popup-opened");
|
||||||
EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
|
EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
|
||||||
|
await waitForClassApplied("auto-body-added-by-scrip");
|
||||||
await onPopupOpened;
|
await onPopupOpened;
|
||||||
is(
|
is(
|
||||||
textInput.value,
|
textInput.value,
|
||||||
|
@ -203,6 +209,7 @@ add_task(async function() {
|
||||||
"auto-body-added-by-script",
|
"auto-body-added-by-script",
|
||||||
"Tab puts the selected item in the input and closes the popup"
|
"Tab puts the selected item in the input and closes the popup"
|
||||||
);
|
);
|
||||||
|
await waitForClassRemoved("auto-body-added-by-scrip");
|
||||||
});
|
});
|
||||||
|
|
||||||
async function checkAutocompleteItems(
|
async function checkAutocompleteItems(
|
||||||
|
@ -224,3 +231,25 @@ function getAutocompleteItems(autocompletePopup) {
|
||||||
el => el.textContent
|
el => el.textContent
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForClassApplied(cls) {
|
||||||
|
info("Wait for class to be applied: " + cls);
|
||||||
|
await SpecialPowers.spawn(gBrowser.selectedBrowser, [cls], async _cls => {
|
||||||
|
return ContentTaskUtils.waitForCondition(() =>
|
||||||
|
content.document.body.classList.contains(_cls)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Wait for debounced functions to be executed
|
||||||
|
await wait(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForClassRemoved(cls) {
|
||||||
|
info("Wait for class to be removed: " + cls);
|
||||||
|
await SpecialPowers.spawn(gBrowser.selectedBrowser, [cls], async _cls => {
|
||||||
|
return ContentTaskUtils.waitForCondition(
|
||||||
|
() => !content.document.body.classList.contains(_cls)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Wait for debounced functions to be executed
|
||||||
|
await wait(200);
|
||||||
|
}
|
||||||
|
|
|
@ -31,13 +31,14 @@ class ClassListPreviewer {
|
||||||
|
|
||||||
this.onNewSelection = this.onNewSelection.bind(this);
|
this.onNewSelection = this.onNewSelection.bind(this);
|
||||||
this.onCheckBoxChanged = this.onCheckBoxChanged.bind(this);
|
this.onCheckBoxChanged = this.onCheckBoxChanged.bind(this);
|
||||||
this.onKeyPress = this.onKeyPress.bind(this);
|
this.onKeyDown = this.onKeyDown.bind(this);
|
||||||
this.onAddElementInputModified = debounce(
|
this.onAddElementInputModified = debounce(
|
||||||
this.onAddElementInputModified,
|
this.onAddElementInputModified,
|
||||||
75,
|
75,
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
this.onCurrentNodeClassChanged = this.onCurrentNodeClassChanged.bind(this);
|
this.onCurrentNodeClassChanged = this.onCurrentNodeClassChanged.bind(this);
|
||||||
|
this.onNodeFrontWillUnset = this.onNodeFrontWillUnset.bind(this);
|
||||||
|
|
||||||
// Create the add class text field.
|
// Create the add class text field.
|
||||||
this.addEl = this.doc.createElement("input");
|
this.addEl = this.doc.createElement("input");
|
||||||
|
@ -47,7 +48,7 @@ class ClassListPreviewer {
|
||||||
"placeholder",
|
"placeholder",
|
||||||
L10N.getStr("inspector.classPanel.newClass.placeholder")
|
L10N.getStr("inspector.classPanel.newClass.placeholder")
|
||||||
);
|
);
|
||||||
this.addEl.addEventListener("keypress", this.onKeyPress);
|
this.addEl.addEventListener("keydown", this.onKeyDown);
|
||||||
this.addEl.addEventListener("input", this.onAddElementInputModified);
|
this.addEl.addEventListener("input", this.onAddElementInputModified);
|
||||||
this.containerEl.appendChild(this.addEl);
|
this.containerEl.appendChild(this.addEl);
|
||||||
|
|
||||||
|
@ -68,12 +69,17 @@ class ClassListPreviewer {
|
||||||
this.addEl.value = item.label;
|
this.addEl.value = item.label;
|
||||||
this.autocompletePopup.hidePopup();
|
this.autocompletePopup.hidePopup();
|
||||||
this.autocompletePopup.clearItems();
|
this.autocompletePopup.clearItems();
|
||||||
|
this.model.previewClass(item.label);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start listening for interesting events.
|
// Start listening for interesting events.
|
||||||
this.inspector.selection.on("new-node-front", this.onNewSelection);
|
this.inspector.selection.on("new-node-front", this.onNewSelection);
|
||||||
|
this.inspector.selection.on(
|
||||||
|
"node-front-will-unset",
|
||||||
|
this.onNodeFrontWillUnset
|
||||||
|
);
|
||||||
this.containerEl.addEventListener("input", this.onCheckBoxChanged);
|
this.containerEl.addEventListener("input", this.onCheckBoxChanged);
|
||||||
this.model.on("current-node-class-changed", this.onCurrentNodeClassChanged);
|
this.model.on("current-node-class-changed", this.onCurrentNodeClassChanged);
|
||||||
|
|
||||||
|
@ -82,7 +88,11 @@ class ClassListPreviewer {
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.inspector.selection.off("new-node-front", this.onNewSelection);
|
this.inspector.selection.off("new-node-front", this.onNewSelection);
|
||||||
this.addEl.removeEventListener("keypress", this.onKeyPress);
|
this.inspector.selection.off(
|
||||||
|
"node-front-will-unset",
|
||||||
|
this.onNodeFrontWillUnset
|
||||||
|
);
|
||||||
|
this.addEl.removeEventListener("keydown", this.onKeyDown);
|
||||||
this.addEl.removeEventListener("input", this.onAddElementInputModified);
|
this.addEl.removeEventListener("input", this.onAddElementInputModified);
|
||||||
this.containerEl.removeEventListener("input", this.onCheckBoxChanged);
|
this.containerEl.removeEventListener("input", this.onCheckBoxChanged);
|
||||||
|
|
||||||
|
@ -181,7 +191,7 @@ class ClassListPreviewer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyPress(event) {
|
onKeyDown(event) {
|
||||||
// If the popup is already open, all the keyboard interaction are handled
|
// If the popup is already open, all the keyboard interaction are handled
|
||||||
// directly by the popup component.
|
// directly by the popup component.
|
||||||
if (this.autocompletePopup.isOpen) {
|
if (this.autocompletePopup.isOpen) {
|
||||||
|
@ -205,6 +215,8 @@ class ClassListPreviewer {
|
||||||
async onAddElementInputModified() {
|
async onAddElementInputModified() {
|
||||||
const newValue = this.addEl.value;
|
const newValue = this.addEl.value;
|
||||||
|
|
||||||
|
this.model.previewClass(newValue);
|
||||||
|
|
||||||
// if the input is empty, let's close the popup, if it was open.
|
// if the input is empty, let's close the popup, if it was open.
|
||||||
if (newValue === "") {
|
if (newValue === "") {
|
||||||
if (this.autocompletePopup.isOpen) {
|
if (this.autocompletePopup.isOpen) {
|
||||||
|
@ -218,7 +230,11 @@ class ClassListPreviewer {
|
||||||
let items = [];
|
let items = [];
|
||||||
try {
|
try {
|
||||||
const classNames = await this.model.getClassNames(newValue);
|
const classNames = await this.model.getClassNames(newValue);
|
||||||
items = classNames.map(className => {
|
items = classNames
|
||||||
|
.filter(
|
||||||
|
className => !this.model.previewClasses.split(" ").includes(className)
|
||||||
|
)
|
||||||
|
.map(className => {
|
||||||
return {
|
return {
|
||||||
preLabel: className.substring(0, newValue.length),
|
preLabel: className.substring(0, newValue.length),
|
||||||
label: className,
|
label: className,
|
||||||
|
@ -262,6 +278,11 @@ class ClassListPreviewer {
|
||||||
onCurrentNodeClassChanged() {
|
onCurrentNodeClassChanged() {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onNodeFrontWillUnset() {
|
||||||
|
this.model.eraseClassPreview();
|
||||||
|
this.addEl.value = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ClassListPreviewer;
|
module.exports = ClassListPreviewer;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче