2013-10-18 14:01:20 +00:00
|
|
|
|
/* 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 {Cc, Ci, Cu} = require("chrome");
|
|
|
|
|
const {colorUtils} = require("devtools/css-color");
|
|
|
|
|
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
|
|
|
|
|
2013-11-11 15:17:41 +00:00
|
|
|
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|
|
|
|
|
2013-11-05 17:00:13 +00:00
|
|
|
|
const MAX_ITERATIONS = 100;
|
2014-01-30 15:04:47 +00:00
|
|
|
|
const REGEX_QUOTES = /^".*?"|^".*|^'.*?'|^'.*/;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
const REGEX_WHITESPACE = /^\s+/;
|
|
|
|
|
const REGEX_FIRST_WORD_OR_CHAR = /^\w+|^./;
|
2014-07-09 21:50:18 +00:00
|
|
|
|
const REGEX_CUBIC_BEZIER = /^linear|^ease-in-out|^ease-in|^ease-out|^ease|^cubic-bezier\(([0-9.\- ]+,){3}[0-9.\- ]+\)/;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
|
2014-12-26 11:40:35 +00:00
|
|
|
|
// CSS variable names are identifiers which the spec defines as follows:
|
|
|
|
|
// 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 (_).
|
2015-03-16 14:55:00 +00:00
|
|
|
|
const REGEX_CSS_VAR = /^\bvar\(\s*--[-_a-zA-Z0-9\u00A0-\u10FFFF]+\s*\)/;
|
2014-12-26 11:40:35 +00:00
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
/**
|
|
|
|
|
* This regex matches:
|
|
|
|
|
* - #F00
|
|
|
|
|
* - #FF0000
|
|
|
|
|
* - hsl()
|
|
|
|
|
* - hsla()
|
|
|
|
|
* - rgb()
|
|
|
|
|
* - rgba()
|
|
|
|
|
* - color names
|
|
|
|
|
*/
|
|
|
|
|
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 () {
|
|
|
|
|
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This regular expression catches all css property names with their trailing
|
|
|
|
|
* spaces and semicolon. This is used to ensure a value is valid for a property
|
|
|
|
|
* name within style="" attributes.
|
|
|
|
|
*/
|
|
|
|
|
loader.lazyGetter(this, "REGEX_ALL_CSS_PROPERTIES", function () {
|
|
|
|
|
let names = DOMUtils.getCSSPropertyNames();
|
|
|
|
|
let pattern = "^(";
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < names.length; i++) {
|
|
|
|
|
if (i > 0) {
|
|
|
|
|
pattern += "|";
|
|
|
|
|
}
|
|
|
|
|
pattern += names[i];
|
|
|
|
|
}
|
|
|
|
|
pattern += ")\\s*:\\s*";
|
|
|
|
|
|
|
|
|
|
return new RegExp(pattern);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This module is used to process text for output by developer tools. This means
|
|
|
|
|
* linking JS files with the debugger, CSS files with the style editor, JS
|
|
|
|
|
* functions with the debugger, placing color swatches next to colors and
|
|
|
|
|
* adding doorhanger previews where possible (images, angles, lengths,
|
|
|
|
|
* border radius, cubic-bezier etc.).
|
|
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
|
|
|
|
* const {OutputParser} = devtools.require("devtools/output-parser");
|
|
|
|
|
*
|
|
|
|
|
* let parser = new OutputParser();
|
|
|
|
|
*
|
|
|
|
|
* parser.parseCssProperty("color", "red"); // Returns document fragment.
|
|
|
|
|
* parser.parseHTMLAttribute("color:red; font-size: 12px;"); // Returns document
|
|
|
|
|
* // fragment.
|
|
|
|
|
*/
|
|
|
|
|
function OutputParser() {
|
|
|
|
|
this.parsed = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exports.OutputParser = OutputParser;
|
|
|
|
|
|
|
|
|
|
OutputParser.prototype = {
|
|
|
|
|
/**
|
|
|
|
|
* Parse a CSS property value given a property name.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} name
|
|
|
|
|
* CSS Property Name
|
|
|
|
|
* @param {String} value
|
|
|
|
|
* CSS Property value
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* Options object. For valid options and default values see
|
|
|
|
|
* _mergeOptions().
|
|
|
|
|
* @return {DocumentFragment}
|
|
|
|
|
* A document fragment containing color swatches etc.
|
|
|
|
|
*/
|
|
|
|
|
parseCssProperty: function(name, value, options={}) {
|
|
|
|
|
options = this._mergeOptions(options);
|
|
|
|
|
|
2014-07-09 21:50:18 +00:00
|
|
|
|
// XXX: This is a quick fix that should stay until bug 977063 gets fixed.
|
|
|
|
|
// It avoids parsing "linear" as a timing-function in "linear-gradient(...)"
|
|
|
|
|
options.expectCubicBezier = ["transition", "transition-timing-function",
|
|
|
|
|
"animation", "animation-timing-function"].indexOf(name) !== -1;
|
|
|
|
|
|
2013-11-11 15:17:41 +00:00
|
|
|
|
if (this._cssPropertySupportsValue(name, value)) {
|
|
|
|
|
return this._parse(value, options);
|
|
|
|
|
}
|
|
|
|
|
this._appendTextNode(value);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
|
2013-11-11 15:17:41 +00:00
|
|
|
|
return this._toDOM();
|
2013-10-18 14:01:20 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parse a string.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} value
|
|
|
|
|
* Text to parse.
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* Options object. For valid options and default values see
|
|
|
|
|
* _mergeOptions().
|
|
|
|
|
* @return {DocumentFragment}
|
2013-11-11 15:17:41 +00:00
|
|
|
|
* A document fragment. Colors will not be parsed.
|
2013-10-18 14:01:20 +00:00
|
|
|
|
*/
|
|
|
|
|
parseHTMLAttribute: function(value, options={}) {
|
2013-11-18 16:07:24 +00:00
|
|
|
|
options.isHTMLAttribute = true;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
options = this._mergeOptions(options);
|
|
|
|
|
|
|
|
|
|
return this._parse(value, options);
|
|
|
|
|
},
|
|
|
|
|
|
2014-02-05 10:53:46 +00:00
|
|
|
|
/**
|
|
|
|
|
* Matches the beginning of the provided string to a css background-image url
|
|
|
|
|
* and return both the whole url(...) match and the url itself.
|
|
|
|
|
* This isn't handled via a regular expression to make sure we can match urls
|
|
|
|
|
* that contain parenthesis easily
|
|
|
|
|
*/
|
|
|
|
|
_matchBackgroundUrl: function(text) {
|
|
|
|
|
let startToken = "url(";
|
|
|
|
|
if (text.indexOf(startToken) !== 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()];
|
|
|
|
|
},
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
/**
|
|
|
|
|
* Parse a string.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} text
|
|
|
|
|
* Text to parse.
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* Options object. For valid options and default values see
|
|
|
|
|
* _mergeOptions().
|
|
|
|
|
* @return {DocumentFragment}
|
2013-11-11 15:17:41 +00:00
|
|
|
|
* A document fragment.
|
2013-10-18 14:01:20 +00:00
|
|
|
|
*/
|
|
|
|
|
_parse: function(text, options={}) {
|
|
|
|
|
text = text.trim();
|
|
|
|
|
this.parsed.length = 0;
|
2013-11-05 17:00:13 +00:00
|
|
|
|
let i = 0;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
|
|
|
|
|
while (text.length > 0) {
|
2013-11-18 16:07:24 +00:00
|
|
|
|
let matched = null;
|
|
|
|
|
|
|
|
|
|
// Prevent this loop from slowing down the browser with too
|
|
|
|
|
// many nodes being appended into output. In practice it is very unlikely
|
|
|
|
|
// that this will ever happen.
|
|
|
|
|
i++;
|
|
|
|
|
if (i > MAX_ITERATIONS) {
|
|
|
|
|
this._appendTextNode(text);
|
|
|
|
|
text = "";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
matched = text.match(REGEX_QUOTES);
|
|
|
|
|
if (matched) {
|
|
|
|
|
let match = matched[0];
|
2013-11-18 16:07:24 +00:00
|
|
|
|
|
|
|
|
|
text = this._trimMatchFromStart(text, match);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
this._appendTextNode(match);
|
2013-11-18 16:07:24 +00:00
|
|
|
|
continue;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-12-26 11:40:35 +00:00
|
|
|
|
matched = text.match(REGEX_CSS_VAR);
|
|
|
|
|
if (matched) {
|
|
|
|
|
let match = matched[0];
|
|
|
|
|
|
|
|
|
|
text = this._trimMatchFromStart(text, match);
|
|
|
|
|
this._appendTextNode(match);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
matched = text.match(REGEX_WHITESPACE);
|
|
|
|
|
if (matched) {
|
|
|
|
|
let match = matched[0];
|
2013-11-18 16:07:24 +00:00
|
|
|
|
|
|
|
|
|
text = this._trimMatchFromStart(text, match);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
this._appendTextNode(match);
|
2013-11-18 16:07:24 +00:00
|
|
|
|
continue;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:53:46 +00:00
|
|
|
|
matched = this._matchBackgroundUrl(text);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
if (matched) {
|
2013-11-11 15:17:41 +00:00
|
|
|
|
let [match, url] = matched;
|
2014-02-05 10:36:02 +00:00
|
|
|
|
text = this._trimMatchFromStart(text, match);
|
2014-02-05 10:53:46 +00:00
|
|
|
|
|
2013-11-11 15:17:41 +00:00
|
|
|
|
this._appendURL(match, url, options);
|
2013-11-18 16:07:24 +00:00
|
|
|
|
continue;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-09 21:50:18 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
matched = text.match(REGEX_ALL_CSS_PROPERTIES);
|
|
|
|
|
if (matched) {
|
2013-11-11 15:17:41 +00:00
|
|
|
|
let [match] = matched;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
|
2013-11-18 16:07:24 +00:00
|
|
|
|
text = this._trimMatchFromStart(text, match);
|
|
|
|
|
this._appendTextNode(match);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
|
2013-11-18 16:07:24 +00:00
|
|
|
|
if (options.isHTMLAttribute) {
|
|
|
|
|
[text] = this._appendColorOnMatch(text, options);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
2013-11-18 16:07:24 +00:00
|
|
|
|
continue;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-18 16:07:24 +00:00
|
|
|
|
if (!options.isHTMLAttribute) {
|
|
|
|
|
let dirty;
|
|
|
|
|
|
|
|
|
|
[text, dirty] = this._appendColorOnMatch(text, options);
|
|
|
|
|
|
|
|
|
|
if (dirty) {
|
|
|
|
|
continue;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-18 16:07:24 +00:00
|
|
|
|
// This test must always be last as it indicates use of an unknown
|
|
|
|
|
// character that needs to be removed to prevent infinite loops.
|
|
|
|
|
matched = text.match(REGEX_FIRST_WORD_OR_CHAR);
|
|
|
|
|
if (matched) {
|
|
|
|
|
let match = matched[0];
|
2013-11-05 17:00:13 +00:00
|
|
|
|
|
2013-11-18 16:07:24 +00:00
|
|
|
|
text = this._trimMatchFromStart(text, match);
|
|
|
|
|
this._appendTextNode(match);
|
2013-11-05 17:00:13 +00:00
|
|
|
|
}
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this._toDOM();
|
|
|
|
|
},
|
|
|
|
|
|
2013-11-18 16:07:24 +00:00
|
|
|
|
/**
|
|
|
|
|
* 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];
|
|
|
|
|
},
|
|
|
|
|
|
2014-07-09 21:50:18 +00:00
|
|
|
|
/**
|
|
|
|
|
* Append a cubic-bezier timing function value to the output
|
|
|
|
|
*
|
|
|
|
|
* @param {String} bezier
|
|
|
|
|
* The cubic-bezier timing function
|
|
|
|
|
* @param {Object} options
|
|
|
|
|
* Options object. For valid options and default values see
|
|
|
|
|
* _mergeOptions()
|
|
|
|
|
*/
|
|
|
|
|
_appendCubicBezier: function(bezier, options) {
|
|
|
|
|
let container = this._createNode("span", {
|
|
|
|
|
"data-bezier": bezier
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (options.bezierSwatchClass) {
|
|
|
|
|
let swatch = this._createNode("span", {
|
|
|
|
|
class: options.bezierSwatchClass
|
|
|
|
|
});
|
|
|
|
|
container.appendChild(swatch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let value = this._createNode("span", {
|
|
|
|
|
class: options.bezierClass
|
|
|
|
|
}, bezier);
|
|
|
|
|
|
|
|
|
|
container.appendChild(value);
|
|
|
|
|
this.parsed.push(container);
|
|
|
|
|
},
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
/**
|
|
|
|
|
* Check if a CSS property supports a specific value.
|
|
|
|
|
*
|
2013-11-11 15:17:41 +00:00
|
|
|
|
* @param {String} name
|
2013-10-18 14:01:20 +00:00
|
|
|
|
* CSS Property name to check
|
2013-11-11 15:17:41 +00:00
|
|
|
|
* @param {String} value
|
2013-10-18 14:01:20 +00:00
|
|
|
|
* CSS Property value to check
|
|
|
|
|
*/
|
2013-11-11 15:17:41 +00:00
|
|
|
|
_cssPropertySupportsValue: function(name, value) {
|
2014-12-17 13:43:30 +00:00
|
|
|
|
return DOMUtils.cssPropertyIsValid(name, value);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
},
|
|
|
|
|
|
2014-01-30 15:04:47 +00:00
|
|
|
|
/**
|
|
|
|
|
* Tests if a given colorObject output by CssColor is valid for parsing.
|
|
|
|
|
* Valid means it's really a color, not any of the CssColor SPECIAL_VALUES
|
|
|
|
|
* except transparent
|
|
|
|
|
*/
|
|
|
|
|
_isValidColor: function(colorObj) {
|
|
|
|
|
return colorObj.valid &&
|
|
|
|
|
(!colorObj.specialValue || colorObj.specialValue === "transparent");
|
|
|
|
|
},
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
/**
|
|
|
|
|
* Append a color to the output.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} color
|
|
|
|
|
* Color to append
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* Options object. For valid options and default values see
|
|
|
|
|
* _mergeOptions().
|
2013-11-11 15:17:41 +00:00
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
* true if the color passed in was valid, false otherwise. Special
|
|
|
|
|
* values such as transparent also return false.
|
2013-10-18 14:01:20 +00:00
|
|
|
|
*/
|
|
|
|
|
_appendColor: function(color, options={}) {
|
2013-11-11 15:17:41 +00:00
|
|
|
|
let colorObj = new colorUtils.CssColor(color);
|
|
|
|
|
|
2014-01-30 15:04:47 +00:00
|
|
|
|
if (this._isValidColor(colorObj)) {
|
2014-06-12 10:02:00 +00:00
|
|
|
|
let container = this._createNode("span", {
|
|
|
|
|
"data-color": color
|
|
|
|
|
});
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
if (options.colorSwatchClass) {
|
2014-06-12 10:02:00 +00:00
|
|
|
|
let swatch = this._createNode("span", {
|
2013-10-18 14:01:20 +00:00
|
|
|
|
class: options.colorSwatchClass,
|
|
|
|
|
style: "background-color:" + color
|
|
|
|
|
});
|
2014-06-12 10:02:00 +00:00
|
|
|
|
container.appendChild(swatch);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
2014-06-12 10:02:00 +00:00
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
if (options.defaultColorType) {
|
2013-11-11 15:17:41 +00:00
|
|
|
|
color = colorObj.toString();
|
2014-06-12 10:02:00 +00:00
|
|
|
|
container.dataset["color"] = color;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
2014-06-12 10:02:00 +00:00
|
|
|
|
|
|
|
|
|
let value = this._createNode("span", {
|
2013-12-03 13:45:29 +00:00
|
|
|
|
class: options.colorClass
|
|
|
|
|
}, color);
|
2014-06-12 10:02:00 +00:00
|
|
|
|
|
|
|
|
|
container.appendChild(value);
|
|
|
|
|
this.parsed.push(container);
|
2013-11-11 15:17:41 +00:00
|
|
|
|
return true;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
2013-11-11 15:17:41 +00:00
|
|
|
|
return false;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
},
|
|
|
|
|
|
2013-11-11 15:17:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* Append a URL to the output.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} match
|
|
|
|
|
* Complete match that may include "url(xxx)"
|
|
|
|
|
* @param {String} url
|
|
|
|
|
* Actual URL
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* Options object. For valid options and default values see
|
|
|
|
|
* _mergeOptions().
|
|
|
|
|
*/
|
|
|
|
|
_appendURL: function(match, url, options={}) {
|
|
|
|
|
if (options.urlClass) {
|
2014-08-11 13:17:52 +00:00
|
|
|
|
this._appendTextNode("url(\"");
|
2013-11-11 15:17:41 +00:00
|
|
|
|
|
|
|
|
|
let href = url;
|
|
|
|
|
if (options.baseURI) {
|
|
|
|
|
href = options.baseURI.resolve(url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._appendNode("a", {
|
|
|
|
|
target: "_blank",
|
|
|
|
|
class: options.urlClass,
|
|
|
|
|
href: href
|
|
|
|
|
}, url);
|
|
|
|
|
|
2014-08-11 13:17:52 +00:00
|
|
|
|
this._appendTextNode("\")");
|
2013-11-11 15:17:41 +00:00
|
|
|
|
} else {
|
2014-08-11 13:17:52 +00:00
|
|
|
|
this._appendTextNode("url(\"" + url + "\")");
|
2013-11-11 15:17:41 +00:00
|
|
|
|
}
|
2013-10-18 14:01:20 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
2014-06-12 10:02:00 +00:00
|
|
|
|
* Create a node.
|
2013-10-18 14:01:20 +00:00
|
|
|
|
*
|
|
|
|
|
* @param {String} tagName
|
|
|
|
|
* Tag type e.g. "div"
|
|
|
|
|
* @param {Object} attributes
|
|
|
|
|
* e.g. {class: "someClass", style: "cursor:pointer"};
|
|
|
|
|
* @param {String} [value]
|
|
|
|
|
* If a value is included it will be appended as a text node inside
|
|
|
|
|
* the tag. This is useful e.g. for span tags.
|
2014-06-12 10:02:00 +00:00
|
|
|
|
* @return {Node} Newly created Node.
|
2013-10-18 14:01:20 +00:00
|
|
|
|
*/
|
2014-06-12 10:02:00 +00:00
|
|
|
|
_createNode: function(tagName, attributes, value="") {
|
2013-10-18 14:01:20 +00:00
|
|
|
|
let win = Services.appShell.hiddenDOMWindow;
|
|
|
|
|
let doc = win.document;
|
2013-11-11 15:17:41 +00:00
|
|
|
|
let node = doc.createElementNS(HTML_NS, tagName);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
let attrs = Object.getOwnPropertyNames(attributes);
|
|
|
|
|
|
|
|
|
|
for (let attr of attrs) {
|
2013-12-03 13:45:29 +00:00
|
|
|
|
if (attributes[attr]) {
|
|
|
|
|
node.setAttribute(attr, attributes[attr]);
|
|
|
|
|
}
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value) {
|
2013-11-11 15:17:41 +00:00
|
|
|
|
let textNode = doc.createTextNode(value);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
node.appendChild(textNode);
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-12 10:02:00 +00:00
|
|
|
|
return node;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Append a node to the output.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} tagName
|
|
|
|
|
* Tag type e.g. "div"
|
|
|
|
|
* @param {Object} attributes
|
|
|
|
|
* e.g. {class: "someClass", style: "cursor:pointer"};
|
|
|
|
|
* @param {String} [value]
|
|
|
|
|
* If a value is included it will be appended as a text node inside
|
|
|
|
|
* the tag. This is useful e.g. for span tags.
|
|
|
|
|
*/
|
|
|
|
|
_appendNode: function(tagName, attributes, value="") {
|
|
|
|
|
let node = this._createNode(tagName, attributes, value);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
this.parsed.push(node);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Append a text node to the output. If the previously output item was a text
|
|
|
|
|
* node then we append the text to that node.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} text
|
|
|
|
|
* Text to append
|
|
|
|
|
*/
|
|
|
|
|
_appendTextNode: function(text) {
|
|
|
|
|
let lastItem = this.parsed[this.parsed.length - 1];
|
2013-11-05 17:00:13 +00:00
|
|
|
|
if (typeof lastItem === "string") {
|
2013-11-18 16:07:24 +00:00
|
|
|
|
this.parsed[this.parsed.length - 1] = lastItem + text;
|
2013-10-18 14:01:20 +00:00
|
|
|
|
} else {
|
2013-11-05 17:00:13 +00:00
|
|
|
|
this.parsed.push(text);
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Take all output and append it into a single DocumentFragment.
|
|
|
|
|
*
|
|
|
|
|
* @return {DocumentFragment}
|
|
|
|
|
* Document Fragment
|
|
|
|
|
*/
|
|
|
|
|
_toDOM: function() {
|
|
|
|
|
let win = Services.appShell.hiddenDOMWindow;
|
|
|
|
|
let doc = win.document;
|
|
|
|
|
let frag = doc.createDocumentFragment();
|
|
|
|
|
|
|
|
|
|
for (let item of this.parsed) {
|
2013-11-05 17:00:13 +00:00
|
|
|
|
if (typeof item === "string") {
|
|
|
|
|
frag.appendChild(doc.createTextNode(item));
|
|
|
|
|
} else {
|
|
|
|
|
frag.appendChild(item);
|
|
|
|
|
}
|
2013-10-18 14:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.parsed.length = 0;
|
|
|
|
|
return frag;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Merges options objects. Default values are set here.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} overrides
|
|
|
|
|
* The option values to override e.g. _mergeOptions({colors: false})
|
|
|
|
|
*
|
|
|
|
|
* Valid options are:
|
|
|
|
|
* - defaultColorType: true // Convert colors to the default type
|
|
|
|
|
* // selected in the options panel.
|
|
|
|
|
* - colorSwatchClass: "" // The class to use for color swatches.
|
2013-12-03 13:45:29 +00:00
|
|
|
|
* - colorClass: "" // The class to use for the color value
|
|
|
|
|
* // that follows the swatch.
|
2014-07-09 21:50:18 +00:00
|
|
|
|
* - bezierSwatchClass: "" // The class to use for bezier swatches.
|
|
|
|
|
* - bezierClass: "" // The class to use for the bezier value
|
|
|
|
|
* // that follows the swatch.
|
2013-11-18 16:07:24 +00:00
|
|
|
|
* - isHTMLAttribute: false // This property indicates whether we
|
|
|
|
|
* // are parsing an HTML attribute value.
|
|
|
|
|
* // When the value is passed in from an
|
|
|
|
|
* // HTML attribute we need to check that
|
|
|
|
|
* // any CSS property values are supported
|
|
|
|
|
* // by the property name before
|
|
|
|
|
* // processing the property value.
|
2013-11-11 15:17:41 +00:00
|
|
|
|
* - urlClass: "" // The class to be used for url() links.
|
|
|
|
|
* - baseURI: "" // A string or nsIURI used to resolve
|
|
|
|
|
* // relative links.
|
2013-10-18 14:01:20 +00:00
|
|
|
|
* @return {Object}
|
|
|
|
|
* Overridden options object
|
|
|
|
|
*/
|
|
|
|
|
_mergeOptions: function(overrides) {
|
|
|
|
|
let defaults = {
|
|
|
|
|
defaultColorType: true,
|
|
|
|
|
colorSwatchClass: "",
|
2013-12-03 13:45:29 +00:00
|
|
|
|
colorClass: "",
|
2014-07-09 21:50:18 +00:00
|
|
|
|
bezierSwatchClass: "",
|
|
|
|
|
bezierClass: "",
|
2013-11-18 16:07:24 +00:00
|
|
|
|
isHTMLAttribute: false,
|
2013-11-11 15:17:41 +00:00
|
|
|
|
urlClass: "",
|
|
|
|
|
baseURI: ""
|
2013-10-18 14:01:20 +00:00
|
|
|
|
};
|
|
|
|
|
|
2013-11-11 15:17:41 +00:00
|
|
|
|
if (typeof overrides.baseURI === "string") {
|
|
|
|
|
overrides.baseURI = Services.io.newURI(overrides.baseURI, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-18 14:01:20 +00:00
|
|
|
|
for (let item in overrides) {
|
|
|
|
|
defaults[item] = overrides[item];
|
|
|
|
|
}
|
|
|
|
|
return defaults;
|
2014-01-30 15:04:47 +00:00
|
|
|
|
}
|
2013-10-18 14:01:20 +00:00
|
|
|
|
};
|