/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* This is a CSS Filter Editor widget used
* for Rule View's filter swatches
*/
const EventEmitter = require("devtools/shared/event-emitter");
const { Cc, Ci } = require("chrome");
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const { LocalizationHelper } = require("devtools/shared/l10n");
const STRINGS_URI = "devtools/client/locales/filterwidget.properties";
const L10N = new LocalizationHelper(STRINGS_URI);
const {cssTokenizer} = require("devtools/shared/css/parsing-utils");
const asyncStorage = require("devtools/shared/async-storage");
loader.lazyGetter(this, "DOMUtils", () => {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
const DEFAULT_FILTER_TYPE = "length";
const UNIT_MAPPING = {
percentage: "%",
length: "px",
angle: "deg",
string: ""
};
const FAST_VALUE_MULTIPLIER = 10;
const SLOW_VALUE_MULTIPLIER = 0.1;
const DEFAULT_VALUE_MULTIPLIER = 1;
const LIST_PADDING = 7;
const LIST_ITEM_HEIGHT = 32;
const filterList = [
{
"name": "blur",
"range": [0, Infinity],
"type": "length"
},
{
"name": "brightness",
"range": [0, Infinity],
"type": "percentage"
},
{
"name": "contrast",
"range": [0, Infinity],
"type": "percentage"
},
{
"name": "drop-shadow",
"placeholder": L10N.getStr("dropShadowPlaceholder"),
"type": "string"
},
{
"name": "grayscale",
"range": [0, 100],
"type": "percentage"
},
{
"name": "hue-rotate",
"range": [0, Infinity],
"type": "angle"
},
{
"name": "invert",
"range": [0, 100],
"type": "percentage"
},
{
"name": "opacity",
"range": [0, 100],
"type": "percentage"
},
{
"name": "saturate",
"range": [0, Infinity],
"type": "percentage"
},
{
"name": "sepia",
"range": [0, 100],
"type": "percentage"
},
{
"name": "url",
"placeholder": "example.svg#c1",
"type": "string"
}
];
// Valid values that shouldn't be parsed for filters.
const SPECIAL_VALUES = new Set(["none", "unset", "initial", "inherit"]);
/**
* A CSS Filter editor widget used to add/remove/modify
* filters.
*
* Normally, it takes a CSS filter value as input, parses it
* and creates the required elements / bindings.
*
* You can, however, use add/remove/update methods manually.
* See each method's comments for more details
*
* @param {nsIDOMNode} el
* The widget container.
* @param {String} value
* CSS filter value
* @param {Function} cssIsValid
* Test whether css name / value is valid.
*/
function CSSFilterEditorWidget(el, value = "", cssIsValid) {
this.doc = el.ownerDocument;
this.win = this.doc.defaultView;
this.el = el;
this._cssIsValid = cssIsValid;
this._addButtonClick = this._addButtonClick.bind(this);
this._removeButtonClick = this._removeButtonClick.bind(this);
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._presetClick = this._presetClick.bind(this);
this._savePreset = this._savePreset.bind(this);
this._togglePresets = this._togglePresets.bind(this);
this._resetFocus = this._resetFocus.bind(this);
// Passed to asyncStorage, requires binding
this.renderPresets = this.renderPresets.bind(this);
this._initMarkup();
this._buildFilterItemMarkup();
this._buildPresetItemMarkup();
this._addEventListeners();
EventEmitter.decorate(this);
this.filters = [];
this.setCssValue(value);
this.renderPresets();
}
exports.CSSFilterEditorWidget = CSSFilterEditorWidget;
CSSFilterEditorWidget.prototype = {
_initMarkup: function () {
let filterListSelectPlaceholder =
L10N.getStr("filterListSelectPlaceholder");
let addNewFilterButton = L10N.getStr("addNewFilterButton");
let presetsToggleButton = L10N.getStr("presetsToggleButton");
let newPresetPlaceholder = L10N.getStr("newPresetPlaceholder");
let savePresetButton = L10N.getStr("savePresetButton");
this.el.innerHTML = `
`;
this.filtersList = this.el.querySelector("#filters");
this.presetsList = this.el.querySelector("#presets");
this.togglePresets = this.el.querySelector("#toggle-presets");
this.filterSelect = this.el.querySelector("select");
this.addPresetButton = this.el.querySelector(".presets-list .add");
this.addPresetInput = this.el.querySelector(".presets-list .footer input");
this.el.querySelector(".presets-list input").value = "";
this._populateFilterSelect();
},
_destroyMarkup: function () {
this._filterItemMarkup.remove();
this.el.remove();
this.el = this.filtersList = this._filterItemMarkup = null;
this.presetsList = this.togglePresets = this.filterSelect = null;
this.addPresetButton = null;
},
destroy: function () {
this._removeEventListeners();
this._destroyMarkup();
},
/**
* Creates