/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- / /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ /* 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/. */ /* Implementation of a service that converts certain vendor-prefixed CSS properties to their unprefixed equivalents, for sites on a whitelist. */ // XXXdholbert whitelist is coming in bug 1132743 "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); function CSSUnprefixingService() { } CSSUnprefixingService.prototype = { // Boilerplate: classID: Components.ID("{f0729490-e15c-4a2f-a3fb-99e1cc946b42}"), _xpcom_factory: XPCOMUtils.generateSingletonFactory(CSSUnprefixingService), QueryInterface: XPCOMUtils.generateQI([Ci.nsICSSUnprefixingService]), // See documentation in nsICSSUnprefixingService.idl generateUnprefixedDeclaration: function(aPropName, aRightHalfOfDecl, aUnprefixedDecl /*out*/) { // Convert our input strings to lower-case, for easier string-matching. // (NOTE: If we ever need to add support for unprefixing properties that // have case-sensitive parts, then we should do these toLowerCase() // conversions in a more targeted way, to avoid breaking those properties.) aPropName = aPropName.toLowerCase(); aRightHalfOfDecl = aRightHalfOfDecl.toLowerCase(); // We have several groups of supported properties: // FIRST GROUP: Properties that can just be handled as aliases: // ============================================================ const propertiesThatAreJustAliases = { "-webkit-background-size": "background-size", "-webkit-box-flex": "flex-grow", "-webkit-box-ordinal-group": "order", "-webkit-box-sizing": "box-sizing", "-webkit-transform": "transform", }; let unprefixedPropName = propertiesThatAreJustAliases[aPropName]; if (unprefixedPropName !== undefined) { aUnprefixedDecl.value = unprefixedPropName + ":" + aRightHalfOfDecl; return true; } // SECOND GROUP: Properties that take a single keyword, where the // unprefixed version takes a different (but analogous) set of keywords: // ===================================================================== const propertiesThatNeedKeywordMapping = { "-webkit-box-align" : { unprefixedPropName : "align-items", valueMap : { "start" : "flex-start", "center" : "center", "end" : "flex-end", "baseline" : "baseline", "stretch" : "stretch" } }, "-webkit-box-orient" : { unprefixedPropName : "flex-direction", valueMap : { "horizontal" : "row", "inline-axis" : "row", "vertical" : "column", "block-axis" : "column" } }, "-webkit-box-pack" : { unprefixedPropName : "justify-content", valueMap : { "start" : "flex-start", "center" : "center", "end" : "flex-end", "justify" : "space-between" } }, }; let propInfo = propertiesThatNeedKeywordMapping[aPropName]; if (typeof(propInfo) != "undefined") { // Regexp for parsing the right half of a declaration, for keyword-valued // properties. Divides the right half of the declaration into: // 1) any leading whitespace // 2) the property value (one or more alphabetical character or hyphen) // 3) anything after that (e.g. "!important", ";") // Then we can look up the appropriate unprefixed-property value for the // value (part 2), and splice that together with the other parts and with // the unprefixed property-name to make the final declaration. const keywordValuedPropertyRegexp = /^(\s*)([a-z\-]+)(.*)/; let parts = keywordValuedPropertyRegexp.exec(aRightHalfOfDecl); if (!parts) { // Failed to parse a keyword out of aRightHalfOfDecl. (It probably has // no alphabetical characters.) return false; } let mappedKeyword = propInfo.valueMap[parts[2]]; if (mappedKeyword === undefined) { // We found a keyword in aRightHalfOfDecl, but we don't have a mapping // to an equivalent keyword for the unprefixed version of the property. return false; } aUnprefixedDecl.value = propInfo.unprefixedPropName + ":" + parts[1] + // any leading whitespace mappedKeyword + parts[3]; // any trailing text (e.g. !important, semicolon, etc) return true; } // THIRD GROUP: Properties that may need arbitrary string-replacement: // =================================================================== const propertiesThatNeedStringReplacement = { // "-webkit-transition" takes a multi-part value. If "-webkit-transform" // appears as part of that value, replace it w/ "transform". // And regardless, we unprefix the "-webkit-transition" property-name. // (We could handle other prefixed properties in addition to 'transform' // here, but in practice "-webkit-transform" is the main one that's // likely to be transitioned & that we're concerned about supporting.) "-webkit-transition": { unprefixedPropName : "transition", stringMap : { "-webkit-transform" : "transform", } }, }; propInfo = propertiesThatNeedStringReplacement[aPropName]; if (typeof(propInfo) != "undefined") { let newRightHalf = aRightHalfOfDecl; for (let strToReplace in propInfo.stringMap) { let replacement = propInfo.stringMap[strToReplace]; newRightHalf = newRightHalf.split(strToReplace).join(replacement); } aUnprefixedDecl.value = propInfo.unprefixedPropName + ":" + newRightHalf; return true; } // No known mapping for property aPropName. return false; }, }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CSSUnprefixingService]);