зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1154809 - rewrite output-parser.js to use CSSLexer; r=pbrosset
This commit is contained in:
Родитель
109c223159
Коммит
2a621cdafe
|
@ -11,28 +11,18 @@ const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
const MAX_ITERATIONS = 100;
|
const MAX_ITERATIONS = 100;
|
||||||
const REGEX_QUOTES = /^".*?"|^".*|^'.*?'|^'.*/;
|
|
||||||
const REGEX_WHITESPACE = /^\s+/;
|
|
||||||
const REGEX_FIRST_WORD_OR_CHAR = /^\w+|^./;
|
|
||||||
const REGEX_CUBIC_BEZIER = /^linear|^ease-in-out|^ease-in|^ease-out|^ease|^cubic-bezier\(([0-9.\- ]+,){3}[0-9.\- ]+\)/;
|
|
||||||
|
|
||||||
// CSS variable names are identifiers which the spec defines as follows:
|
const BEZIER_KEYWORDS = ["linear", "ease-in-out", "ease-in", "ease-out", "ease"];
|
||||||
// In CSS, identifiers (including element names, classes, and IDs in
|
|
||||||
// selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646
|
|
||||||
// characters U+00A0 and higher, plus the hyphen (-) and the underscore (_).
|
|
||||||
const REGEX_CSS_VAR = /^\bvar\(\s*--[-_a-zA-Z0-9\u00A0-\u10FFFF]+\s*\)/;
|
|
||||||
|
|
||||||
/**
|
// Functions that accept a color argument.
|
||||||
* This regex matches:
|
const COLOR_TAKING_FUNCTIONS = ["linear-gradient",
|
||||||
* - #F00
|
"-moz-linear-gradient",
|
||||||
* - #FF0000
|
"repeating-linear-gradient",
|
||||||
* - hsl()
|
"-moz-repeating-linear-gradient",
|
||||||
* - hsla()
|
"radial-gradient",
|
||||||
* - rgb()
|
"-moz-radial-gradient",
|
||||||
* - rgba()
|
"repeating-radial-gradient",
|
||||||
* - color names
|
"-moz-repeating-radial-gradient"];
|
||||||
*/
|
|
||||||
const REGEX_ALL_COLORS = /^#[0-9a-fA-F]{3}\b|^#[0-9a-fA-F]{6}\b|^hsl\(.*?\)|^hsla\(.*?\)|^rgba?\(.*?\)|^[a-zA-Z-]+/;
|
|
||||||
|
|
||||||
loader.lazyGetter(this, "DOMUtils", function () {
|
loader.lazyGetter(this, "DOMUtils", function () {
|
||||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||||
|
@ -94,29 +84,44 @@ OutputParser.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches the beginning of the provided string to a css background-image url
|
* Given an initial FUNCTION token, read tokens from |tokenStream|
|
||||||
* and return both the whole url(...) match and the url itself.
|
* and collect all the (non-comment) text. Return the collected
|
||||||
* This isn't handled via a regular expression to make sure we can match urls
|
* text. The function token and the close paren are included in the
|
||||||
* that contain parenthesis easily
|
* result.
|
||||||
|
*
|
||||||
|
* @param {CSSToken} initialToken
|
||||||
|
* The FUNCTION token.
|
||||||
|
* @param {String} text
|
||||||
|
* The original CSS text.
|
||||||
|
* @param {CSSLexer} tokenStream
|
||||||
|
* The token stream from which to read.
|
||||||
|
* @return {String}
|
||||||
|
* The text of body of the function call.
|
||||||
*/
|
*/
|
||||||
_matchBackgroundUrl: function(text) {
|
_collectFunctionText: function(initialToken, text, tokenStream) {
|
||||||
let startToken = "url(";
|
let result = text.substring(initialToken.startOffset,
|
||||||
if (text.indexOf(startToken) !== 0) {
|
initialToken.endOffset);
|
||||||
return null;
|
let depth = 1;
|
||||||
|
while (depth > 0) {
|
||||||
|
let token = tokenStream.nextToken();
|
||||||
|
if (!token) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (token.tokenType === "comment") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result += text.substring(token.startOffset, token.endOffset);
|
||||||
|
if (token.tokenType === "symbol") {
|
||||||
|
if (token.text === "(") {
|
||||||
|
++depth;
|
||||||
|
} else if (token.text === ")") {
|
||||||
|
--depth;
|
||||||
|
}
|
||||||
|
} else if (token.tokenType === "function") {
|
||||||
|
++depth;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
let uri = text.substring(startToken.length).trim();
|
|
||||||
let quote = uri.substring(0, 1);
|
|
||||||
if (quote === "'" || quote === '"') {
|
|
||||||
uri = uri.substring(1, uri.search(new RegExp(quote + "\\s*\\)")));
|
|
||||||
} else {
|
|
||||||
uri = uri.substring(0, uri.indexOf(")"));
|
|
||||||
quote = "";
|
|
||||||
}
|
|
||||||
let end = startToken + quote + uri;
|
|
||||||
text = text.substring(0, text.indexOf(")", end.length) + 1);
|
|
||||||
|
|
||||||
return [text, uri.trim()];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,142 +138,96 @@ OutputParser.prototype = {
|
||||||
_parse: function(text, options={}) {
|
_parse: function(text, options={}) {
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
this.parsed.length = 0;
|
this.parsed.length = 0;
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
while (text.length > 0) {
|
if (options.expectFilter) {
|
||||||
let matched = null;
|
this._appendFilter(text, options);
|
||||||
|
} else {
|
||||||
// Prevent this loop from slowing down the browser with too
|
let tokenStream = DOMUtils.getCSSLexer(text);
|
||||||
// many nodes being appended into output. In practice it is very unlikely
|
let i = 0;
|
||||||
// that this will ever happen.
|
while (true) {
|
||||||
i++;
|
let token = tokenStream.nextToken();
|
||||||
if (i > MAX_ITERATIONS) {
|
if (!token)
|
||||||
this._appendTextNode(text);
|
break;
|
||||||
text = "";
|
if (token.tokenType === "comment") {
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.expectFilter) {
|
|
||||||
this._appendFilter(text, options);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
matched = text.match(REGEX_QUOTES);
|
|
||||||
if (matched) {
|
|
||||||
let match = matched[0];
|
|
||||||
|
|
||||||
text = this._trimMatchFromStart(text, match);
|
|
||||||
this._appendTextNode(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
matched = text.match(REGEX_CSS_VAR);
|
|
||||||
if (matched) {
|
|
||||||
let match = matched[0];
|
|
||||||
|
|
||||||
text = this._trimMatchFromStart(text, match);
|
|
||||||
this._appendTextNode(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
matched = text.match(REGEX_WHITESPACE);
|
|
||||||
if (matched) {
|
|
||||||
let match = matched[0];
|
|
||||||
|
|
||||||
text = this._trimMatchFromStart(text, match);
|
|
||||||
this._appendTextNode(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
matched = this._matchBackgroundUrl(text);
|
|
||||||
if (matched) {
|
|
||||||
let [match, url] = matched;
|
|
||||||
text = this._trimMatchFromStart(text, match);
|
|
||||||
|
|
||||||
this._appendURL(match, url, options);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.expectCubicBezier) {
|
|
||||||
matched = text.match(REGEX_CUBIC_BEZIER);
|
|
||||||
if (matched) {
|
|
||||||
let match = matched[0];
|
|
||||||
text = this._trimMatchFromStart(text, match);
|
|
||||||
|
|
||||||
this._appendCubicBezier(match, options);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (options.supportsColor) {
|
// Prevent this loop from slowing down the browser with too
|
||||||
let dirty;
|
// many nodes being appended into output. In practice it is very unlikely
|
||||||
|
// that this will ever happen.
|
||||||
[text, dirty] = this._appendColorOnMatch(text, options);
|
i++;
|
||||||
|
if (i > MAX_ITERATIONS) {
|
||||||
if (dirty) {
|
this._appendTextNode(text.substring(token.startOffset,
|
||||||
|
token.endOffset));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// This test must always be last as it indicates use of an unknown
|
switch (token.tokenType) {
|
||||||
// character that needs to be removed to prevent infinite loops.
|
case "function": {
|
||||||
matched = text.match(REGEX_FIRST_WORD_OR_CHAR);
|
if (COLOR_TAKING_FUNCTIONS.indexOf(token.text) >= 0) {
|
||||||
if (matched) {
|
// The function can accept a color argument, and we know
|
||||||
let match = matched[0];
|
// it isn't special in some other way. So, we let it
|
||||||
|
// through to the ordinary parsing loop so that colors
|
||||||
|
// can be handled in a single place.
|
||||||
|
this._appendTextNode(text.substring(token.startOffset,
|
||||||
|
token.endOffset));
|
||||||
|
} else {
|
||||||
|
let functionText = this._collectFunctionText(token, text,
|
||||||
|
tokenStream);
|
||||||
|
|
||||||
text = this._trimMatchFromStart(text, match);
|
if (options.expectCubicBezier && token.text === "cubic-bezier") {
|
||||||
this._appendTextNode(match);
|
this._appendCubicBezier(functionText, options);
|
||||||
|
} else if (options.supportsColor &&
|
||||||
|
DOMUtils.isValidCSSColor(functionText)) {
|
||||||
|
this._appendColor(functionText, options);
|
||||||
|
} else {
|
||||||
|
this._appendTextNode(functionText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ident":
|
||||||
|
if (options.expectCubicBezier &&
|
||||||
|
BEZIER_KEYWORDS.indexOf(token.text) >= 0) {
|
||||||
|
this._appendCubicBezier(token.text, options);
|
||||||
|
} else if (options.supportsColor &&
|
||||||
|
DOMUtils.isValidCSSColor(token.text)) {
|
||||||
|
this._appendColor(token.text, options);
|
||||||
|
} else {
|
||||||
|
this._appendTextNode(text.substring(token.startOffset,
|
||||||
|
token.endOffset));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "id":
|
||||||
|
case "hash": {
|
||||||
|
let original = text.substring(token.startOffset, token.endOffset);
|
||||||
|
if (options.supportsColor && DOMUtils.isValidCSSColor(original)) {
|
||||||
|
this._appendColor(original, options);
|
||||||
|
} else {
|
||||||
|
this._appendTextNode(original);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "url":
|
||||||
|
case "bad_url":
|
||||||
|
this._appendURL(text.substring(token.startOffset, token.endOffset),
|
||||||
|
token.text, options);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this._appendTextNode(text.substring(token.startOffset,
|
||||||
|
token.endOffset));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._toDOM();
|
return this._toDOM();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience function to make the parser a little more readable.
|
|
||||||
*
|
|
||||||
* @param {String} text
|
|
||||||
* Main text
|
|
||||||
* @param {String} match
|
|
||||||
* Text to remove from the beginning
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* The string passed as 'text' with 'match' stripped from the start.
|
|
||||||
*/
|
|
||||||
_trimMatchFromStart: function(text, match) {
|
|
||||||
return text.substr(match.length);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there is a color match and append it if it is valid.
|
|
||||||
*
|
|
||||||
* @param {String} text
|
|
||||||
* Main text
|
|
||||||
* @param {Object} options
|
|
||||||
* Options object. For valid options and default values see
|
|
||||||
* _mergeOptions().
|
|
||||||
*
|
|
||||||
* @return {Array}
|
|
||||||
* An array containing the remaining text and a dirty flag. This array
|
|
||||||
* is designed for deconstruction using [text, dirty].
|
|
||||||
*/
|
|
||||||
_appendColorOnMatch: function(text, options) {
|
|
||||||
let dirty;
|
|
||||||
let matched = text.match(REGEX_ALL_COLORS);
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
let match = matched[0];
|
|
||||||
if (this._appendColor(match, options)) {
|
|
||||||
text = this._trimMatchFromStart(text, match);
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [text, dirty];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append a cubic-bezier timing function value to the output
|
* Append a cubic-bezier timing function value to the output
|
||||||
*
|
*
|
||||||
|
|
Загрузка…
Ссылка в новой задаче