123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- /* 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";
- const { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
- const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
- const { Task } = require("devtools/shared/task");
- const { CSS_PROPERTIES_DB } = require("devtools/shared/css/properties-db");
- const { cssColors } = require("devtools/shared/css/color-db");
- /**
- * Build up a regular expression that matches a CSS variable token. This is an
- * ident token that starts with two dashes "--".
- *
- * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
- */
- var NON_ASCII = "[^\\x00-\\x7F]";
- var ESCAPE = "\\\\[^\n\r]";
- var FIRST_CHAR = ["[_a-z]", NON_ASCII, ESCAPE].join("|");
- var TRAILING_CHAR = ["[_a-z0-9-]", NON_ASCII, ESCAPE].join("|");
- var IS_VARIABLE_TOKEN = new RegExp(`^--(${FIRST_CHAR})(${TRAILING_CHAR})*$`,
- "i");
- /**
- * Check that this is a CSS variable.
- *
- * @param {String} input
- * @return {Boolean}
- */
- function isCssVariable(input) {
- return !!input.match(IS_VARIABLE_TOKEN);
- }
- var cachedCssProperties = new WeakMap();
- /**
- * The CssProperties front provides a mechanism to have a one-time asynchronous
- * load of a CSS properties database. This is then fed into the CssProperties
- * interface that provides synchronous methods for finding out what CSS
- * properties the current server supports.
- */
- const CssPropertiesFront = FrontClassWithSpec(cssPropertiesSpec, {
- initialize: function (client, { cssPropertiesActor }) {
- Front.prototype.initialize.call(this, client, {actor: cssPropertiesActor});
- this.manage(this);
- }
- });
- /**
- * Query the feature supporting status in the featureSet.
- *
- * @param {Hashmap} featureSet the feature set hashmap
- * @param {String} feature the feature name string
- * @return {Boolean} has the feature or not
- */
- function hasFeature(featureSet, feature) {
- if (feature in featureSet) {
- return featureSet[feature];
- }
- return false;
- }
- /**
- * Ask questions to a CSS database. This class does not care how the database
- * gets loaded in, only the questions that you can ask to it.
- * Prototype functions are bound to 'this' so they can be passed around as helper
- * functions.
- *
- * @param {Object} db
- * A database of CSS properties
- * @param {Object} inheritedList
- * The key is the property name, the value is whether or not
- * that property is inherited.
- */
- function CssProperties(db) {
- this.properties = db.properties;
- this.pseudoElements = db.pseudoElements;
- // supported feature
- this.cssColor4ColorFunction = hasFeature(db.supportedFeature,
- "css-color-4-color-function");
- this.isKnown = this.isKnown.bind(this);
- this.isInherited = this.isInherited.bind(this);
- this.supportsType = this.supportsType.bind(this);
- this.isValidOnClient = this.isValidOnClient.bind(this);
- this.supportsCssColor4ColorFunction =
- this.supportsCssColor4ColorFunction.bind(this);
- // A weakly held dummy HTMLDivElement to test CSS properties on the client.
- this._dummyElements = new WeakMap();
- }
- CssProperties.prototype = {
- /**
- * Checks to see if the property is known by the browser. This function has
- * `this` already bound so that it can be passed around by reference.
- *
- * @param {String} property The property name to be checked.
- * @return {Boolean}
- */
- isKnown(property) {
- return !!this.properties[property] || isCssVariable(property);
- },
- /**
- * Quickly check if a CSS name/value combo is valid on the client.
- *
- * @param {String} Property name.
- * @param {String} Property value.
- * @param {Document} The client's document object.
- * @return {Boolean}
- */
- isValidOnClient(name, value, doc) {
- let dummyElement = this._dummyElements.get(doc);
- if (!dummyElement) {
- dummyElement = doc.createElement("div");
- this._dummyElements.set(doc, dummyElement);
- }
- // `!important` is not a valid value when setting a style declaration in the
- // CSS Object Model.
- const sanitizedValue = ("" + value).replace(/!\s*important\s*$/, "");
- // Test the style on the element.
- dummyElement.style[name] = sanitizedValue;
- const isValid = !!dummyElement.style[name];
- // Reset the state of the dummy element;
- dummyElement.style[name] = "";
- return isValid;
- },
- /**
- * Get a function that will check the validity of css name/values for a given document.
- * Useful for injecting isValidOnClient into components when needed.
- *
- * @param {Document} The client's document object.
- * @return {Function} this.isValidOnClient with the document pre-set.
- */
- getValidityChecker(doc) {
- return (name, value) => this.isValidOnClient(name, value, doc);
- },
- /**
- * Checks to see if the property is an inherited one.
- *
- * @param {String} property The property name to be checked.
- * @return {Boolean}
- */
- isInherited(property) {
- return this.properties[property] && this.properties[property].isInherited;
- },
- /**
- * Checks if the property supports the given CSS type.
- * CSS types should come from devtools/shared/css/properties-db.js' CSS_TYPES.
- *
- * @param {String} property The property to be checked.
- * @param {Number} type One of the type values from CSS_TYPES.
- * @return {Boolean}
- */
- supportsType(property, type) {
- return this.properties[property] && this.properties[property].supports.includes(type);
- },
- /**
- * Gets the CSS values for a given property name.
- *
- * @param {String} property The property to use.
- * @return {Array} An array of strings.
- */
- getValues(property) {
- return this.properties[property] ? this.properties[property].values : [];
- },
- /**
- * Gets the CSS property names.
- *
- * @return {Array} An array of strings.
- */
- getNames(property) {
- return Object.keys(this.properties);
- },
- /**
- * Return a list of subproperties for the given property. If |name|
- * does not name a valid property, an empty array is returned. If
- * the property is not a shorthand property, then array containing
- * just the property itself is returned.
- *
- * @param {String} name The property to query
- * @return {Array} An array of subproperty names.
- */
- getSubproperties(name) {
- if (this.isKnown(name)) {
- if (this.properties[name] && this.properties[name].subproperties) {
- return this.properties[name].subproperties;
- }
- return [name];
- }
- return [];
- },
- /**
- * Checking for the css-color-4 color function support.
- *
- * @return {Boolean} Return true if the server supports css-color-4 color function.
- */
- supportsCssColor4ColorFunction() {
- return this.cssColor4ColorFunction;
- },
- };
- /**
- * Create a CssProperties object with a fully loaded CSS database. The
- * CssProperties interface can be queried synchronously, but the initialization
- * is potentially async and should be handled up-front when the tool is created.
- *
- * The front is returned only with this function so that it can be destroyed
- * once the toolbox is destroyed.
- *
- * @param {Toolbox} The current toolbox.
- * @returns {Promise} Resolves to {cssProperties, cssPropertiesFront}.
- */
- const initCssProperties = Task.async(function* (toolbox) {
- const client = toolbox.target.client;
- if (cachedCssProperties.has(client)) {
- return cachedCssProperties.get(client);
- }
- let db, front;
- // Get the list dynamically if the cssProperties actor exists.
- if (toolbox.target.hasActor("cssProperties")) {
- front = CssPropertiesFront(client, toolbox.target.form);
- const serverDB = yield front.getCSSDatabase();
- // Ensure the database was returned in a format that is understood.
- // Older versions of the protocol could return a blank database.
- if (!serverDB.properties && !serverDB.margin) {
- db = CSS_PROPERTIES_DB;
- } else {
- db = serverDB;
- }
- } else {
- // The target does not support this actor, so require a static list of supported
- // properties.
- db = CSS_PROPERTIES_DB;
- }
- const cssProperties = new CssProperties(normalizeCssData(db));
- cachedCssProperties.set(client, {cssProperties, front});
- return {cssProperties, front};
- });
- /**
- * Synchronously get a cached and initialized CssProperties.
- *
- * @param {Toolbox} The current toolbox.
- * @returns {CssProperties}
- */
- function getCssProperties(toolbox) {
- if (!cachedCssProperties.has(toolbox.target.client)) {
- throw new Error("The CSS database has not been initialized, please make " +
- "sure initCssDatabase was called once before for this " +
- "toolbox.");
- }
- return cachedCssProperties.get(toolbox.target.client).cssProperties;
- }
- /**
- * Get a client-side CssProperties. This is useful for dependencies in tests, or parts
- * of the codebase that don't particularly need to match every known CSS property on
- * the target.
- * @return {CssProperties}
- */
- function getClientCssProperties() {
- return new CssProperties(normalizeCssData(CSS_PROPERTIES_DB));
- }
- /**
- * Even if the target has the cssProperties actor, the returned data may not be in the
- * same shape or have all of the data we need. This normalizes the data and fills in
- * any missing information like color values.
- *
- * @return {Object} The normalized CSS database.
- */
- function normalizeCssData(db) {
- if (db !== CSS_PROPERTIES_DB) {
- // Firefox 49's getCSSDatabase() just returned the properties object, but
- // now it returns an object with multiple types of CSS information.
- if (!db.properties) {
- db = { properties: db };
- }
- // Fill in any missing DB information from the static database.
- db = Object.assign({}, CSS_PROPERTIES_DB, db);
- for (let name in db.properties) {
- // Skip the current property if we can't find it in CSS_PROPERTIES_DB.
- if (typeof CSS_PROPERTIES_DB.properties[name] !== "object") {
- continue;
- }
- // Add "supports" information to the css properties if it's missing.
- if (!db.properties.color.supports) {
- db.properties[name].supports = CSS_PROPERTIES_DB.properties[name].supports;
- }
- // Add "values" information to the css properties if it's missing.
- if (!db.properties.color.values) {
- db.properties[name].values = CSS_PROPERTIES_DB.properties[name].values;
- }
- // Add "subproperties" information to the css properties if it's missing.
- if (!db.properties.background.subproperties) {
- db.properties[name].subproperties =
- CSS_PROPERTIES_DB.properties[name].subproperties;
- }
- }
- }
- reattachCssColorValues(db);
- // If there is no supportedFeature in db, create an empty one.
- if (!db.supportedFeature) {
- db.supportedFeature = {};
- }
- return db;
- }
- /**
- * Color values are omitted to save on space. Add them back here.
- * @param {Object} The CSS database.
- */
- function reattachCssColorValues(db) {
- if (db.properties.color.values[0] === "COLOR") {
- const colors = Object.keys(cssColors);
- for (let name in db.properties) {
- const property = db.properties[name];
- // "values" can be undefined if {name} was not found in CSS_PROPERTIES_DB.
- if (property.values && property.values[0] === "COLOR") {
- property.values.shift();
- property.values = property.values.concat(colors).sort();
- }
- }
- }
- }
- module.exports = {
- CssPropertiesFront,
- CssProperties,
- getCssProperties,
- getClientCssProperties,
- initCssProperties
- };
|