diff --git a/browser/devtools/shared/test/browser.ini b/browser/devtools/shared/test/browser.ini index b5da569d9686..312b121b7a87 100644 --- a/browser/devtools/shared/test/browser.ini +++ b/browser/devtools/shared/test/browser.ini @@ -27,6 +27,9 @@ support-files = [browser_filter-editor-05.js] [browser_filter-editor-06.js] [browser_filter-editor-07.js] +[browser_filter-editor-08.js] +[browser_filter-editor-09.js] +[browser_filter-editor-10.js] [browser_flame-graph-01.js] [browser_flame-graph-02.js] [browser_flame-graph-03a.js] diff --git a/browser/devtools/shared/test/browser_filter-editor-08.js b/browser/devtools/shared/test/browser_filter-editor-08.js new file mode 100644 index 000000000000..f041419a9f7c --- /dev/null +++ b/browser/devtools/shared/test/browser_filter-editor-08.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the Filter Editor Widget inputs increase/decrease value using +// arrow keys, applying multiplier using alt/shift on number-type filters + +const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml"; +const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget"); + +const FAST_VALUE_MULTIPLIER = 10; +const SLOW_VALUE_MULTIPLIER = 0.1; +const DEFAULT_VALUE_MULTIPLIER = 1; + +add_task(function*() { + yield promiseTab("about:blank"); + let [host, win, doc] = yield createHost("bottom", TEST_URI); + + const container = doc.querySelector("#container"); + const initialValue = "blur(2px)"; + let widget = new CSSFilterEditorWidget(container, initialValue); + + let value = 2; + + triggerKey = triggerKey.bind(widget); + + info("Test simple arrow keys"); + triggerKey(40); + + value -= DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), `${value}px`, + "Should decrease value using down arrow"); + + triggerKey(38); + + value += DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), `${value}px`, + "Should decrease value using down arrow"); + + info("Test shift key multiplier"); + triggerKey(38, "shiftKey"); + + value += FAST_VALUE_MULTIPLIER; + is(widget.getValueAt(0), `${value}px`, + "Should increase value by fast multiplier using up arrow"); + + triggerKey(40, "shiftKey"); + + value -= FAST_VALUE_MULTIPLIER; + is(widget.getValueAt(0), `${value}px`, + "Should decrease value by fast multiplier using down arrow"); + + info("Test alt key multiplier"); + triggerKey(38, "altKey"); + + value += SLOW_VALUE_MULTIPLIER; + is(widget.getValueAt(0), `${value}px`, + "Should increase value by slow multiplier using up arrow"); + + triggerKey(40, "altKey"); + + value -= SLOW_VALUE_MULTIPLIER; + is(widget.getValueAt(0), `${value}px`, + "Should decrease value by slow multiplier using down arrow"); + + triggerKey = null; +}); + +// Triggers the specified keyCode and modifier key on +// first filter's input +function triggerKey(key, modifier) { + const filter = this.el.querySelector(".filters").children[0]; + const input = filter.querySelector("input"); + + this._keyDown({ + target: input, + keyCode: key, + [modifier]: true, + preventDefault: function() {} + }); +} diff --git a/browser/devtools/shared/test/browser_filter-editor-09.js b/browser/devtools/shared/test/browser_filter-editor-09.js new file mode 100644 index 000000000000..f3d6b7c01ffe --- /dev/null +++ b/browser/devtools/shared/test/browser_filter-editor-09.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the Filter Editor Widget inputs increase/decrease value when cursor is +// on a number using arrow keys, applying multiplier using alt/shift on strings + +const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml"; +const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget"); + +const FAST_VALUE_MULTIPLIER = 10; +const SLOW_VALUE_MULTIPLIER = 0.1; +const DEFAULT_VALUE_MULTIPLIER = 1; + +add_task(function*() { + yield promiseTab("about:blank"); + let [host, win, doc] = yield createHost("bottom", TEST_URI); + + const container = doc.querySelector("#container"); + const initialValue = "drop-shadow(rgb(0, 0, 0) 1px 1px 0px)"; + let widget = new CSSFilterEditorWidget(container, initialValue); + widget.el.querySelector("input").setSelectionRange(13, 13); + + let value = 1; + + triggerKey = triggerKey.bind(widget); + + info("Test simple arrow keys"); + triggerKey(40); + + value -= DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should decrease value using down arrow"); + + triggerKey(38); + + value += DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should decrease value using down arrow"); + + info("Test shift key multiplier"); + triggerKey(38, "shiftKey"); + + value += FAST_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should increase value by fast multiplier using up arrow"); + + triggerKey(40, "shiftKey"); + + value -= FAST_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should decrease value by fast multiplier using down arrow"); + + info("Test alt key multiplier"); + triggerKey(38, "altKey"); + + value += SLOW_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should increase value by slow multiplier using up arrow"); + + triggerKey(40, "altKey"); + + value -= SLOW_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should decrease value by slow multiplier using down arrow"); + + triggerKey(40, "shiftKey"); + + value -= FAST_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should decrease to negative"); + + triggerKey(40); + + value -= DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should decrease negative numbers correctly"); + + triggerKey(38); + + value += DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should increase negative values correctly"); + + triggerKey(40, "altKey"); + triggerKey(40, "altKey"); + + value -= SLOW_VALUE_MULTIPLIER * 2; + is(widget.getValueAt(0), val(value), + "Should decrease float numbers correctly"); + + triggerKey(38, "altKey"); + + value += SLOW_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should increase float numbers correctly"); + + triggerKey = null; +}); + +// Triggers the specified keyCode and modifier key on +// first filter's input +function triggerKey(key, modifier) { + const filter = this.el.querySelector(".filters").children[0]; + const input = filter.querySelector("input"); + + this._keyDown({ + target: input, + keyCode: key, + [modifier]: true, + preventDefault: function() {} + }); +} + +function val(value) { + let v = value.toFixed(1); + + if (v.indexOf(".0") > -1) { + v = v.slice(0, -2); + } + return `rgb(0, 0, 0) ${v}px 1px 0px`; +} diff --git a/browser/devtools/shared/test/browser_filter-editor-10.js b/browser/devtools/shared/test/browser_filter-editor-10.js new file mode 100644 index 000000000000..2965ceea8304 --- /dev/null +++ b/browser/devtools/shared/test/browser_filter-editor-10.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the Filter Editor Widget inputs increase/decrease value when cursor is +// on a number using arrow keys if cursor is behind/mid/after the number strings + +const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml"; +const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget"); + +const FAST_VALUE_MULTIPLIER = 10; +const SLOW_VALUE_MULTIPLIER = 0.1; +const DEFAULT_VALUE_MULTIPLIER = 1; + +add_task(function*() { + yield promiseTab("about:blank"); + let [host, win, doc] = yield createHost("bottom", TEST_URI); + + const container = doc.querySelector("#container"); + const initialValue = "drop-shadow(rgb(0, 0, 0) 10px 1px 0px)"; + let widget = new CSSFilterEditorWidget(container, initialValue); + const input = widget.el.querySelector("input"); + + let value = 10; + + triggerKey = triggerKey.bind(widget); + + info("Test increment/decrement of string-type numbers without selection"); + + input.setSelectionRange(14, 14); + triggerKey(40); + + value -= DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should work with cursor in the middle of number"); + + input.setSelectionRange(13, 13); + triggerKey(38); + + value += DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should work with cursor before the number"); + + input.setSelectionRange(15, 15); + triggerKey(40); + + value -= DEFAULT_VALUE_MULTIPLIER; + is(widget.getValueAt(0), val(value), + "Should work with cursor after the number"); + + info("Test increment/decrement of string-type numbers with a selection"); + + input.setSelectionRange(13, 15); + triggerKey(38); + input.setSelectionRange(13, 18); + triggerKey(38); + + value += DEFAULT_VALUE_MULTIPLIER * 2; + is(widget.getValueAt(0), val(value), + "Should work if a there is a selection, starting with the number"); + + triggerKey = null; +}); + +// Triggers the specified keyCode and modifier key on +// first filter's input +function triggerKey(key, modifier) { + const filter = this.el.querySelector(".filters").children[0]; + const input = filter.querySelector("input"); + + this._keyDown({ + target: input, + keyCode: key, + [modifier]: true, + preventDefault: function() {} + }); +} + +function val(value) { + let v = value.toFixed(1); + + if (v.indexOf(".0") > -1) { + v = v.slice(0, -2); + } + return `rgb(0, 0, 0) ${v}px 1px 0px`; +} diff --git a/browser/devtools/shared/widgets/FilterWidget.js b/browser/devtools/shared/widgets/FilterWidget.js index 51c6a99d1f68..8d35d254be28 100644 --- a/browser/devtools/shared/widgets/FilterWidget.js +++ b/browser/devtools/shared/widgets/FilterWidget.js @@ -113,6 +113,7 @@ function CSSFilterEditorWidget(el, value = "") { this._mouseMove = this._mouseMove.bind(this); this._mouseUp = this._mouseUp.bind(this); this._mouseDown = this._mouseDown.bind(this); + this._keyDown = this._keyDown.bind(this); this._input = this._input.bind(this); this._initMarkup(); @@ -195,6 +196,7 @@ CSSFilterEditorWidget.prototype = { this.addButton.addEventListener("click", this._addButtonClick); this.list.addEventListener("click", this._removeButtonClick); this.list.addEventListener("mousedown", this._mouseDown); + this.list.addEventListener("keydown", this._keyDown); // These events are event delegators for // drag-drop re-ordering and label-dragging @@ -205,9 +207,77 @@ CSSFilterEditorWidget.prototype = { this.list.addEventListener("input", this._input); }, + _getFilterElementIndex: function(el) { + return [...this.list.children].indexOf(el); + }, + + _keyDown: function(e) { + if (e.target.tagName.toLowerCase() !== "input" || + (e.keyCode !== 40 && e.keyCode !== 38)) { + return; + } + let input = e.target; + + const direction = e.keyCode === 40 ? -1 : 1; + + let multiplier = DEFAULT_VALUE_MULTIPLIER; + if (e.altKey) { + multiplier = SLOW_VALUE_MULTIPLIER; + } else if (e.shiftKey) { + multiplier = FAST_VALUE_MULTIPLIER; + } + + const filterEl = e.target.closest(".filter"); + const index = this._getFilterElementIndex(filterEl); + const filter = this.filters[index]; + + // Filters that have units are number-type filters. For them, + // the value can be incremented/decremented simply. + // For other types of filters (e.g. drop-shadow) we need to check + // if the keypress happened close to a number first. + if (filter.unit) { + let startValue = parseFloat(e.target.value); + let value = startValue + direction * multiplier; + + const [min, max] = this._definition(filter.name).range; + value = value < min ? min : + value > max ? max : value; + + input.value = fixFloat(value); + + this.updateValueAt(index, value); + } else { + let selectionStart = input.selectionStart; + let num = getNeighbourNumber(input.value, selectionStart); + if (!num) { + return; + } + + let {start, end, value} = num; + + let split = input.value.split(""); + let computed = fixFloat(value + direction * multiplier), + dotIndex = computed.indexOf(".0"); + if (dotIndex > -1) { + computed = computed.slice(0, -2); + + selectionStart = selectionStart > start + dotIndex ? + start + dotIndex : + selectionStart; + } + split.splice(start, end - start, computed); + + value = split.join(""); + input.value = value; + this.updateValueAt(index, value); + input.setSelectionRange(selectionStart, selectionStart); + } + e.preventDefault(); + }, + _input: function(e) { let filterEl = e.target.closest(".filter"), - index = [...this.list.children].indexOf(filterEl), + index = this._getFilterElementIndex(filterEl), filter = this.filters[index], def = this._definition(filter.name); @@ -231,7 +301,7 @@ CSSFilterEditorWidget.prototype = { } else if (e.target.classList.contains("devtools-draglabel")) { let label = e.target, input = filterEl.querySelector("input"), - index = [...this.list.children].indexOf(filterEl); + index = this._getFilterElementIndex(filterEl); this._dragging = { index, label, input, @@ -273,7 +343,7 @@ CSSFilterEditorWidget.prototype = { } let filterEl = e.target.closest(".filter"); - let index = [...this.list.children].indexOf(filterEl); + let index = this._getFilterElementIndex(filterEl); this.removeAt(index); }, @@ -622,6 +692,7 @@ CSSFilterEditorWidget.prototype = { this.addButton.removeEventListener("click", this._addButtonClick); this.list.removeEventListener("click", this._removeButtonClick); this.list.removeEventListener("mousedown", this._mouseDown); + this.list.removeEventListener("keydown", this._keyDown); // These events are used for drag drop re-ordering this.win.removeEventListener("mousemove", this._mouseMove); @@ -718,3 +789,40 @@ function tokenizeComputedFilter(css) { return filters; } + +/** + * Finds neighbour number characters of an index in a string + * the numbers may be floats (containing dots) + * It's assumed that the value given to this function is a valid number + * + * @param {String} string + * The string containing numbers + * @param {Number} index + * The index to look for neighbours for + * @return {Object} + * returns null if no number is found + * value: The number found + * start: The number's starting index + * end: The number's ending index + */ +function getNeighbourNumber(string, index) { + if (!/\d/.test(string)) { + return null; + } + + let left = /-?[0-9.]*$/.exec(string.slice(0, index)), + right = /-?[0-9.]*/.exec(string.slice(index)); + + left = left ? left[0] : ""; + right = right ? right[0] : ""; + + if (!right && !left) { + return null; + } + + return { + value: fixFloat(left + right, true), + start: index - left.length, + end: index + right.length + }; +}