Bug 1132748 part 2: Import cssfixme gradient-unprefixing code. r=hallvors
authorDaniel Holbert <dholbert@cs.stanford.edu>
Thu, 07 May 2015 09:04:42 -0700
changeset 242776 cc88f4422a6f18cb8a55d87c18d165e116b7e8f8
parent 242775 522d010bd1ff0641fb215423689a56d7d41af92c
child 242777 7f5726cc7249006cfb4db3bc9f728d9f85307a51
push id59503
push userdholbert@mozilla.com
push dateThu, 07 May 2015 16:05:40 +0000
treeherdermozilla-inbound@7a63a3b1bd61 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershallvors
bugs1132748
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1132748 part 2: Import cssfixme gradient-unprefixing code. r=hallvors Source: https://github.com/hallvors/css-fixme/blob/fd91a1e259/cssfixme.htm
layout/style/CSSUnprefixingService.js
--- a/layout/style/CSSUnprefixingService.js
+++ b/layout/style/CSSUnprefixingService.js
@@ -162,9 +162,207 @@ CSSUnprefixingService.prototype = {
     //    aUnprefixedFuncName.value = "radial-gradient";
     //    aUnprefixedFuncBody.value = "lime, green";
     //    return true;
 
     return false;
   },
 };
 
+// XXXdholbert BEGIN IMPORTED CODE
+// Source: https://github.com/hallvors/css-fixme/blob/fd91a1e259/cssfixme.htm
+function createFixupGradientDeclaration(decl, parent){
+    var value = decl.value.trim(), newValue='', prop = decl.property.replace(/-webkit-/, '');
+    // -webkit-gradient(<type>, <point> [, <radius>]?, <point> [, <radius>]? [, <stop>]*)
+    // fff -webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f6f6f6));
+    // Sometimes there is code before the -webkit-gradient, for example when it's part of a more complex background: shorthand declaration
+    // we'll extract and keep any stuff before -webkit-gradient before we try parsing the gradient part
+    var head = value.substr(0, value.indexOf('-webkit-gradient'));
+    if(head){
+        value = value.substr(head.length);
+    }
+    var m = value.match(/-webkit-gradient\s*\(\s*(linear|radial)\s*(.*)/);
+    if(m){ // yay, really old syntax...
+
+        // extracting the values..
+        var parts = oldGradientParser(value), type; //GM_log(JSON.stringify(parts, null, 2))
+        for(var i = 0; i < parts.length; i++){
+            if(!parts[i].args)continue;
+            if(parts[i].name === '-webkit-gradient'){
+                type = parts[i].args[0].name;
+                newValue += type + '-gradient('; // radial or linear
+            }
+            newValue += standardizeOldGradientArgs(type, parts[i].args.slice(1));
+            newValue += ')' // end of gradient method
+            if (i < parts.length - 1) {
+                newValue += ', '
+            }
+        }
+    }else{ // we're dealing with more modern syntax - should be somewhat easier, at least for linear gradients.
+        // Fix three things: remove -webkit-, add 'to ' before reversed top/bottom keywords, recalculate deg-values
+        // -webkit-linear-gradient( [ [ <angle> | [top | bottom] || [left | right] ],]? <color-stop>[, <color-stop>]+);
+        newValue = value.replace(/-webkit-/, '');
+        // Keywords top, bottom, left, right: can be stand-alone or combined pairwise but in any order ('top left' or 'left top')
+        // These give the starting edge or corner in the -webkit syntax. The standardised equivalent is 'to ' plus opposite values
+        newValue = newValue.replace(/(top|bottom|left|right)+\s*(top|bottom|left|right)*/, function(str){
+            var words = str.split(/\s+/);
+            for(var i=0; i<words.length; i++){
+                switch(words[i].toLowerCase()){
+                    case 'top':
+                        words[i] = 'bottom';
+                        break;
+                    case 'bottom':
+                        words[i] = 'top';
+                        break;
+                    case 'left':
+                        words[i] = 'right';
+                        break;
+                    case 'right':
+                        words[i] = 'left';
+                }
+            }
+            str = words.join(' ');
+            return ( 'to ' + str);
+        });
+
+        newValue = newValue.replace(/\d+deg/, function (val) {
+             return (360 - (parseInt(val)-90))+'deg';
+         });
+
+    }
+    // Similar to the code for "head" above..
+    var tail = value.substr(value.lastIndexOf(')')+1);
+    if( tail && tail.trim() !== ','){ // sometimes the gradient is a part of a more complex declaration (for example an image shorthand with stuff like no-repeat added), and what's after the gradient needs to be included
+        newValue += tail;
+    }
+    if(head){
+        newValue = head + newValue;
+    }
+    // GM_log(newValue)
+    return {type:'declaration', property:prop, value:newValue, _fxjsdefined:true};
+}
+
+function oldGradientParser(str){
+    /** This method takes a legacy -webkit-gradient() method call and parses it
+        to pull out the values, function names and their arguments.
+        It returns something like [{name:'-webkit-gradient',args:[{name:'linear'}, {name:'top left'} ... ]}]
+    */
+    var objs = [{}], path=[], current, word='', separator_chars = [',', '(', ')'];
+    current = objs[0], path[0] = objs;
+    //str = str.replace(/\s*\(/g, '('); // sorry, ws in front of ( would make parsing a lot harder
+    for(var i = 0; i < str.length; i++){
+        if(separator_chars.indexOf(str[i]) === -1){
+            word += str[i];
+        }else{ // now we have a "separator" - presumably we've also got a "word" or value
+            current.name = word.trim();
+            //GM_log(word+' '+path.length+' '+str[i])
+            word = '';
+            if(str[i] === '('){ // we assume the 'word' is a function, for example -webkit-gradient() or rgb(), so we create a place to record the arguments
+                if(!('args' in current)){
+                   current.args = [];
+                }
+                current.args.push({});
+                path.push(current.args);
+                current = current.args[current.args.length - 1];
+                path.push(current);
+            }else if(str[i] === ')'){ // function is ended, no more arguments - go back to appending details to the previous branch of the tree
+                current = path.pop(); // drop 'current'
+                current = path.pop(); // drop 'args' reference
+            }else{
+                path.pop(); // remove 'current' object from path, we have no arguments to add
+                var current_parent = path[path.length - 1] || objs; // last object on current path refers to array that contained the previous "current"
+                current_parent.push({}); // we need a new object to hold this "word" or value
+                current = current_parent[current_parent.length - 1]; // that object is now the 'current'
+                path.push(current);
+//GM_log(path.length)
+            }
+        }
+    }
+
+    return objs;
+}
+
+/* Given an array of args for "-webkit-gradient(...)" returned by
+ * oldGradientParser(), this function constructs a string representing the
+ * equivalent arguments for a standard "linear-gradient(...)" or
+ * "radial-gradient(...)" expression.
+ *
+ * @param type  Either 'linear' or 'radial'.
+ * @param args  An array of args for a "-webkit-gradient(...)" expression,
+ *              provided by oldGradientParser() (not including gradient type).
+ */
+function standardizeOldGradientArgs(type, args){
+    var stdArgStr = "";
+    var stops = [];
+    if(/^linear/.test(type)){
+        // linear gradient, args 1 and 2 tend to be start/end keywords
+        var points = [].concat(args[0].name.split(/\s+/), args[1].name.split(/\s+/)); // example: [left, top, right, top]
+        // Old webkit syntax "uses a two-point syntax that lets you explicitly state where a linear gradient starts and ends"
+        // if start/end keywords are percentages, let's massage the values a little more..
+        var rxPercTest = /\d+\%/;
+        if(rxPercTest.test(points[0]) || points[0] == 0){
+            var startX = parseInt(points[0]), startY = parseInt(points[1]), endX = parseInt(points[2]), endY = parseInt(points[3]);
+            stdArgStr +=  ((Math.atan2(endY- startY, endX - startX)) * (180 / Math.PI)+90) + 'deg';
+        }else{
+            if(points[1] === points[3]){ // both 'top' or 'bottom, this linear gradient goes left-right
+                stdArgStr += 'to ' + points[2];
+            }else if(points[0] === points[2]){ // both 'left' or 'right', this linear gradient goes top-bottom
+                stdArgStr += 'to ' + points[3];
+            }else if(points[1] === 'top'){ // diagonal gradient - from top left to opposite corner is 135deg
+                stdArgStr += '135deg';
+            }else{
+                stdArgStr += '45deg';
+            }
+        }
+
+    }else if(/^radial/i.test(type)){ // oooh, radial gradients..
+        stdArgStr += 'circle ' + args[3].name.replace(/(\d+)$/, '$1px') + ' at ' + args[0].name.replace(/(\d+) /, '$1px ').replace(/(\d+)$/, '$1px');
+    }
+
+    var toColor;
+    for(var j = type === 'linear' ? 2 : 4; j < args.length; j++){
+        var position, color, colorIndex;
+        if(args[j].name === 'color-stop'){
+            position = args[j].args[0].name;
+            colorIndex = 1;
+        }else if (args[j].name === 'to') {
+            position = '100%';
+            colorIndex = 0;
+        }else if (args[j].name === 'from') {
+            position = '0%';
+            colorIndex = 0;
+        };
+        if (position.indexOf('%') === -1) { // original Safari syntax had 0.5 equivalent to 50%
+            position = (parseFloat(position) * 100) +'%';
+        };
+        color = args[j].args[colorIndex].name;
+        if (args[j].args[colorIndex].args) { // the color is itself a function call, like rgb()
+            color += '(' + colorValue(args[j].args[colorIndex].args) + ')';
+        };
+        if (args[j].name === 'from'){
+            stops.unshift(color + ' ' + position);
+        }else if(args[j].name === 'to'){
+            toColor = color;
+        }else{
+            stops.push(color + ' ' + position);
+        }
+    }
+
+    // translating values to right syntax
+    for(var j = 0; j < stops.length; j++){
+        stdArgStr += ', ' + stops[j];
+    }
+    if(toColor){
+        stdArgStr += ', ' + toColor + ' 100%';
+    }
+    return stdArgStr;
+}
+
+function colorValue(obj){
+    var ar = [];
+    for (var i = 0; i < obj.length; i++) {
+        ar.push(obj[i].name);
+    };
+    return ar.join(', ');
+}
+// XXXdholbert END IMPORTED CODE
+
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CSSUnprefixingService]);