mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 964255 - Introduces a css declarations parser in the rule-view and fixes background-url edition; r=bgrins
This commit is contained in:
parent
e74d99b6d0
commit
93bd77e340
153
browser/devtools/styleinspector/css-parsing-utils.js
Normal file
153
browser/devtools/styleinspector/css-parsing-utils.js
Normal file
@ -0,0 +1,153 @@
|
||||
/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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 cssTokenizer = require("devtools/sourceeditor/css-tokenizer");
|
||||
|
||||
/**
|
||||
* Returns the string enclosed in quotes
|
||||
*/
|
||||
function quoteString(string) {
|
||||
let hasDoubleQuotes = string.contains('"');
|
||||
let hasSingleQuotes = string.contains("'");
|
||||
|
||||
if (hasDoubleQuotes && !hasSingleQuotes) {
|
||||
// In this case, no escaping required, just enclose in single-quotes
|
||||
return "'" + string + "'";
|
||||
}
|
||||
|
||||
// In all other cases, enclose in double-quotes, and escape any double-quote
|
||||
// that may be in the string
|
||||
return '"' + string.replace(/"/g, '\"') + '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of CSS declarations given an string.
|
||||
* For example, parseDeclarations("width: 1px; height: 1px") would return
|
||||
* [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
|
||||
*
|
||||
* The input string is assumed to only contain declarations so { and } characters
|
||||
* will be treated as part of either the property or value, depending where it's
|
||||
* found.
|
||||
*
|
||||
* @param {string} inputString
|
||||
* An input string of CSS
|
||||
* @return {Array} an array of objects with the following signature:
|
||||
* [{"name": string, "value": string, "priority": string}, ...]
|
||||
*/
|
||||
function parseDeclarations(inputString) {
|
||||
let tokens = cssTokenizer(inputString);
|
||||
|
||||
let declarations = [{name: "", value: "", priority: ""}];
|
||||
|
||||
let current = "", hasBang = false, lastProp;
|
||||
for (let token of tokens) {
|
||||
lastProp = declarations[declarations.length - 1];
|
||||
|
||||
if (token.tokenType === ":") {
|
||||
if (!lastProp.name) {
|
||||
// Set the current declaration name if there's no name yet
|
||||
lastProp.name = current.trim();
|
||||
current = "";
|
||||
hasBang = false;
|
||||
} else {
|
||||
// Otherwise, just append ':' to the current value (declaration value
|
||||
// with colons)
|
||||
current += ":";
|
||||
}
|
||||
} else if (token.tokenType === ";") {
|
||||
lastProp.value = current.trim();
|
||||
current = "";
|
||||
hasBang = false;
|
||||
declarations.push({name: "", value: "", priority: ""});
|
||||
} else {
|
||||
switch(token.tokenType) {
|
||||
case "IDENT":
|
||||
if (token.value === "important" && hasBang) {
|
||||
lastProp.priority = "important";
|
||||
hasBang = false;
|
||||
} else {
|
||||
if (hasBang) {
|
||||
current += "!";
|
||||
}
|
||||
current += token.value;
|
||||
}
|
||||
break;
|
||||
case "WHITESPACE":
|
||||
current += " ";
|
||||
break;
|
||||
case "DIMENSION":
|
||||
current += token.repr;
|
||||
break;
|
||||
case "HASH":
|
||||
current += "#" + token.value;
|
||||
break;
|
||||
case "URL":
|
||||
current += "url(" + quoteString(token.value) + ")";
|
||||
break;
|
||||
case "FUNCTION":
|
||||
current += token.value + "(";
|
||||
break;
|
||||
case ")":
|
||||
current += token.tokenType;
|
||||
break;
|
||||
case "EOF":
|
||||
break;
|
||||
case "DELIM":
|
||||
if (token.value === "!") {
|
||||
hasBang = true;
|
||||
} else {
|
||||
current += token.value;
|
||||
}
|
||||
break;
|
||||
case "STRING":
|
||||
current += quoteString(token.value);
|
||||
break;
|
||||
case "{":
|
||||
case "}":
|
||||
current += token.tokenType;
|
||||
break;
|
||||
default:
|
||||
current += token.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle whatever trailing properties or values might still be there
|
||||
if (current) {
|
||||
if (!lastProp.name) {
|
||||
// Trailing property found, e.g. p1:v1;p2:v2;p3
|
||||
lastProp.name = current.trim();
|
||||
} else {
|
||||
// Trailing value found, i.e. value without an ending ;
|
||||
lastProp.value += current.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove declarations that have neither a name nor a value
|
||||
declarations = declarations.filter(prop => prop.name || prop.value);
|
||||
|
||||
return declarations;
|
||||
};
|
||||
exports.parseDeclarations = parseDeclarations;
|
||||
|
||||
/**
|
||||
* Expects a single CSS value to be passed as the input and parses the value
|
||||
* and priority.
|
||||
*
|
||||
* @param {string} value The value from the text editor.
|
||||
* @return {object} an object with 'value' and 'priority' properties.
|
||||
*/
|
||||
function parseSingleValue(value) {
|
||||
let declaration = parseDeclarations("a: " + value + ";")[0];
|
||||
return {
|
||||
value: declaration ? declaration.value : "",
|
||||
priority: declaration ? declaration.priority : ""
|
||||
};
|
||||
};
|
||||
exports.parseSingleValue = parseSingleValue;
|
@ -14,7 +14,8 @@ const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles"
|
||||
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
const {Tooltip, SwatchColorPickerTooltip} = require("devtools/shared/widgets/Tooltip");
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
|
||||
const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
|
||||
const {parseSingleValue, parseDeclarations} = require("devtools/styleinspector/css-parsing-utils");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -582,7 +583,7 @@ Rule.prototype = {
|
||||
|
||||
let promise = aModifications.apply().then(() => {
|
||||
let cssProps = {};
|
||||
for (let cssProp of parseCSSText(this.style.cssText)) {
|
||||
for (let cssProp of parseDeclarations(this.style.cssText)) {
|
||||
cssProps[cssProp.name] = cssProp;
|
||||
}
|
||||
|
||||
@ -692,7 +693,7 @@ Rule.prototype = {
|
||||
_getTextProperties: function() {
|
||||
let textProps = [];
|
||||
let store = this.elementStyle.store;
|
||||
let props = parseCSSText(this.style.cssText);
|
||||
let props = parseDeclarations(this.style.cssText);
|
||||
for (let prop of props) {
|
||||
let name = prop.name;
|
||||
if (this.inherited && !domUtils.isInheritedProperty(name)) {
|
||||
@ -1850,17 +1851,13 @@ RuleEditor.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deal with adding declarations later (once editor has been destroyed).
|
||||
// If aValue is just a name, will make a new property with empty value.
|
||||
this.multipleAddedProperties = parseCSSText(aValue);
|
||||
if (!this.multipleAddedProperties.length) {
|
||||
this.multipleAddedProperties = [{
|
||||
name: aValue,
|
||||
value: "",
|
||||
priority: ""
|
||||
}];
|
||||
}
|
||||
// parseDeclarations allows for name-less declarations, but in the present
|
||||
// case, we're creating a new declaration, it doesn't make sense to accept
|
||||
// these entries
|
||||
this.multipleAddedProperties = parseDeclarations(aValue).filter(d => d.name);
|
||||
|
||||
// Blur the editor field now and deal with adding declarations later when
|
||||
// the field gets destroyed (see _newPropertyDestroy)
|
||||
this.editor.input.blur();
|
||||
},
|
||||
|
||||
@ -2263,17 +2260,16 @@ TextPropertyEditor.prototype = {
|
||||
if (aValue.trim() === "") {
|
||||
this.remove();
|
||||
} else {
|
||||
|
||||
// Adding multiple rules inside of name field overwrites the current
|
||||
// property with the first, then adds any more onto the property list.
|
||||
let properties = parseCSSText(aValue);
|
||||
if (properties.length > 0) {
|
||||
this.prop.setName(properties[0].name);
|
||||
this.prop.setValue(properties[0].value, properties[0].priority);
|
||||
let properties = parseDeclarations(aValue);
|
||||
|
||||
this.ruleEditor.addProperties(properties.slice(1), this.prop);
|
||||
} else {
|
||||
this.prop.setName(aValue);
|
||||
if (properties.length) {
|
||||
this.prop.setName(properties[0].name);
|
||||
if (properties.length > 1) {
|
||||
this.prop.setValue(properties[0].value, properties[0].priority);
|
||||
this.ruleEditor.addProperties(properties.slice(1), this.prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2320,7 +2316,7 @@ TextPropertyEditor.prototype = {
|
||||
let {propertiesToAdd,firstValue} = this._getValueAndExtraProperties(aValue);
|
||||
|
||||
// First, set this property value (common case, only modified a property)
|
||||
let val = parseCSSValue(firstValue);
|
||||
let val = parseSingleValue(firstValue);
|
||||
this.prop.setValue(val.value, val.priority);
|
||||
this.removeOnRevert = false;
|
||||
this.committed.value = this.prop.value;
|
||||
@ -2356,36 +2352,31 @@ TextPropertyEditor.prototype = {
|
||||
* firstValue: A string containing a simple value, like
|
||||
* "red" or "100px!important"
|
||||
* propertiesToAdd: An array with additional properties, following the
|
||||
* parseCSSText format of {name,value,priority}
|
||||
* parseDeclarations format of {name,value,priority}
|
||||
*/
|
||||
_getValueAndExtraProperties: function(aValue) {
|
||||
// The inplace editor will prevent manual typing of multiple properties,
|
||||
// but we need to deal with the case during a paste event.
|
||||
// Adding multiple properties inside of value editor sets value with the
|
||||
// first, then adds any more onto the property list (below this property).
|
||||
let properties = parseCSSText(aValue);
|
||||
let propertiesToAdd = [];
|
||||
let firstValue = aValue;
|
||||
let propertiesToAdd = [];
|
||||
|
||||
if (properties.length > 0) {
|
||||
// If text like "red; width: 1px;" was entered in, handle this as two
|
||||
// separate properties (setting value here to red and adding a new prop).
|
||||
let propertiesNoName = parseCSSText("a:" + aValue);
|
||||
let enteredValueFirst = propertiesNoName.length > properties.length;
|
||||
let properties = parseDeclarations(aValue);
|
||||
|
||||
let firstProp = properties[0];
|
||||
propertiesToAdd = properties.slice(1);
|
||||
|
||||
if (enteredValueFirst) {
|
||||
firstProp = propertiesNoName[0];
|
||||
propertiesToAdd = propertiesNoName.slice(1);
|
||||
// Check to see if the input string can be parsed as multiple properties
|
||||
if (properties.length) {
|
||||
// Get the first property value (if any), and any remaining properties (if any)
|
||||
if (!properties[0].name && properties[0].value) {
|
||||
firstValue = properties[0].value;
|
||||
propertiesToAdd = properties.slice(1);
|
||||
}
|
||||
// In some cases, the value could be a property:value pair itself.
|
||||
// Join them as one value string and append potentially following properties
|
||||
else if (properties[0].name && properties[0].value) {
|
||||
firstValue = properties[0].name + ": " + properties[0].value;
|
||||
propertiesToAdd = properties.slice(1);
|
||||
}
|
||||
|
||||
// If "red; width: 1px", then set value to "red"
|
||||
// If "color: red; width: 1px;", then set value to "color: red;"
|
||||
firstValue = enteredValueFirst ?
|
||||
firstProp.value + "!" + firstProp.priority :
|
||||
firstProp.name + ": " + firstProp.value + "!" + firstProp.priority;
|
||||
}
|
||||
|
||||
return {
|
||||
@ -2395,7 +2386,7 @@ TextPropertyEditor.prototype = {
|
||||
},
|
||||
|
||||
_applyNewValue: function(aValue) {
|
||||
let val = parseCSSValue(aValue);
|
||||
let val = parseSingleValue(aValue);
|
||||
// Any property should be removed if has an empty value.
|
||||
if (val.value.trim() === "") {
|
||||
this.remove();
|
||||
@ -2419,7 +2410,7 @@ TextPropertyEditor.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let val = parseCSSValue(aValue);
|
||||
let val = parseSingleValue(aValue);
|
||||
|
||||
// Live previewing the change without committing just yet, that'll be done in _onValueDone
|
||||
// If it was not a valid value, apply an empty string to reset the live preview
|
||||
@ -2439,7 +2430,7 @@ TextPropertyEditor.prototype = {
|
||||
isValid: function(aValue) {
|
||||
let name = this.prop.name;
|
||||
let value = typeof aValue == "undefined" ? this.prop.value : aValue;
|
||||
let val = parseCSSValue(value);
|
||||
let val = parseSingleValue(value);
|
||||
|
||||
let style = this.doc.createElementNS(HTML_NS, "div").style;
|
||||
let prefs = Services.prefs;
|
||||
@ -2605,64 +2596,14 @@ function throttle(func, wait, scope) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull priority (!important) out of the value provided by a
|
||||
* value editor.
|
||||
*
|
||||
* @param {string} aValue
|
||||
* The value from the text editor.
|
||||
* @return {object} an object with 'value' and 'priority' properties.
|
||||
*/
|
||||
function parseCSSValue(aValue) {
|
||||
let pieces = aValue.split("!", 2);
|
||||
return {
|
||||
value: pieces[0].trim(),
|
||||
priority: (pieces.length > 1 ? pieces[1].trim() : "")
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of CSS properties given an input string
|
||||
* For example, parseCSSText("width: 1px; height: 1px") would return
|
||||
* [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
|
||||
*
|
||||
* @param {string} aCssText
|
||||
* An input string of CSS
|
||||
* @return {Array} an array of objects with the following signature:
|
||||
* [{"name": string, "value": string, "priority": string}, ...]
|
||||
*/
|
||||
function parseCSSText(aCssText) {
|
||||
let lines = aCssText.match(CSS_LINE_RE);
|
||||
let props = [];
|
||||
|
||||
[].forEach.call(lines, (line, i) => {
|
||||
let [, name, value, priority] = CSS_PROP_RE.exec(line) || [];
|
||||
|
||||
// If this is ending with an unfinished line, add it onto the end
|
||||
// with an empty value
|
||||
if (!name && line && i > 0) {
|
||||
name = line;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
props.push({
|
||||
name: name.trim(),
|
||||
value: value || "",
|
||||
priority: priority || ""
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler that causes a blur on the target if the input has
|
||||
* multiple CSS properties as the value.
|
||||
*/
|
||||
function blurOnMultipleProperties(e) {
|
||||
setTimeout(() => {
|
||||
if (parseCSSText(e.target.value).length) {
|
||||
let props = parseDeclarations(e.target.value);
|
||||
if (props.length > 1) {
|
||||
e.target.blur();
|
||||
}
|
||||
}, 0);
|
||||
|
@ -6,6 +6,8 @@ let doc;
|
||||
let ruleWindow;
|
||||
let ruleView;
|
||||
let inspector;
|
||||
let TEST_URL = 'url("http://example.com/browser/browser/devtools/' +
|
||||
'styleinspector/test/test-image.png")';
|
||||
|
||||
function startTest()
|
||||
{
|
||||
@ -140,7 +142,7 @@ function testEditProperty()
|
||||
let value = idRuleEditor.rule.domRule._rawStyle().getPropertyValue("border-color");
|
||||
is(value, "red", "border-color should have been set.");
|
||||
is(propEditor.isValid(), true, "red should be a valid entry");
|
||||
finishTest();
|
||||
testEditPropertyWithColon();
|
||||
}));
|
||||
});
|
||||
|
||||
@ -159,6 +161,43 @@ function testEditProperty()
|
||||
ruleWindow);
|
||||
}
|
||||
|
||||
function testEditPropertyWithColon()
|
||||
{
|
||||
let idRuleEditor = ruleView.element.children[1]._ruleEditor;
|
||||
let propEditor = idRuleEditor.rule.textProps[0].editor;
|
||||
waitForEditorFocus(propEditor.element, function onNewElement(aEditor) {
|
||||
is(inplaceEditor(propEditor.nameSpan), aEditor, "Next focused editor should be the name editor.");
|
||||
let input = aEditor.input;
|
||||
waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
|
||||
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
|
||||
input = aEditor.input;
|
||||
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
|
||||
|
||||
waitForEditorBlur(aEditor, function() {
|
||||
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
|
||||
let value = idRuleEditor.rule.domRule._rawStyle().getPropertyValue("background-image");
|
||||
is(value, TEST_URL, "background-image should have been set.");
|
||||
is(propEditor.isValid(), true, "the test URL should be a valid entry");
|
||||
finishTest();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
for (let ch of (TEST_URL + ";")) {
|
||||
EventUtils.sendChar(ch, ruleWindow);
|
||||
}
|
||||
}));
|
||||
});
|
||||
for (let ch of "background-image:") {
|
||||
EventUtils.sendChar(ch, ruleWindow);
|
||||
}
|
||||
});
|
||||
|
||||
EventUtils.synthesizeMouse(propEditor.nameSpan, 32, 1,
|
||||
{ },
|
||||
ruleWindow);
|
||||
}
|
||||
|
||||
function finishTest()
|
||||
{
|
||||
inspector = ruleWindow = ruleView = null;
|
||||
|
@ -28,13 +28,15 @@ function selectNewElement()
|
||||
let newElement = doc.createElement("div");
|
||||
newElement.textContent = "Test Element";
|
||||
doc.body.appendChild(newElement);
|
||||
inspector.selection.setNode(newElement);
|
||||
|
||||
inspector.selection.setNode(newElement, "test");
|
||||
let def = promise.defer();
|
||||
ruleView.element.addEventListener("CssRuleViewRefreshed", function changed() {
|
||||
ruleView.element.removeEventListener("CssRuleViewRefreshed", changed);
|
||||
elementRuleEditor = ruleView.element.children[0]._ruleEditor;
|
||||
def.resolve();
|
||||
});
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
|
@ -5,3 +5,4 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['browser.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
|
||||
|
@ -0,0 +1,206 @@
|
||||
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
// Simple test
|
||||
{
|
||||
input: "p:v;",
|
||||
expected: [{name: "p", value: "v", priority: ""}]
|
||||
},
|
||||
// Simple test
|
||||
{
|
||||
input: "this:is;a:test;",
|
||||
expected: [
|
||||
{name: "this", value: "is", priority: ""},
|
||||
{name: "a", value: "test", priority: ""}
|
||||
]
|
||||
},
|
||||
// Test a single declaration with semi-colon
|
||||
{
|
||||
input: "name:value;",
|
||||
expected: [{name: "name", value: "value", priority: ""}]
|
||||
},
|
||||
// Test a single declaration without semi-colon
|
||||
{
|
||||
input: "name:value",
|
||||
expected: [{name: "name", value: "value", priority: ""}]
|
||||
},
|
||||
// Test multiple declarations separated by whitespaces and carriage returns and tabs
|
||||
{
|
||||
input: "p1 : v1 ; \t\t \n p2:v2; \n\n\n\n\t p3 : v3;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: ""},
|
||||
{name: "p2", value: "v2", priority: ""},
|
||||
{name: "p3", value: "v3", priority: ""},
|
||||
]
|
||||
},
|
||||
// Test simple priority
|
||||
{
|
||||
input: "p1: v1; p2: v2 !important;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: ""},
|
||||
{name: "p2", value: "v2", priority: "important"}
|
||||
]
|
||||
},
|
||||
// Test simple priority
|
||||
{
|
||||
input: "p1: v1 !important; p2: v2",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: "important"},
|
||||
{name: "p2", value: "v2", priority: ""}
|
||||
]
|
||||
},
|
||||
// Test simple priority
|
||||
{
|
||||
input: "p1: v1 ! important; p2: v2 ! important;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: "important"},
|
||||
{name: "p2", value: "v2", priority: "important"}
|
||||
]
|
||||
},
|
||||
// Test invalid priority
|
||||
{
|
||||
input: "p1: v1 important;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1 important", priority: ""}
|
||||
]
|
||||
},
|
||||
// Test various types of background-image urls
|
||||
{
|
||||
input: "background-image: url(../../relative/image.png)",
|
||||
expected: [{name: "background-image", value: "url(\"../../relative/image.png\")", priority: ""}]
|
||||
},
|
||||
{
|
||||
input: "background-image: url(http://site.com/test.png)",
|
||||
expected: [{name: "background-image", value: "url(\"http://site.com/test.png\")", priority: ""}]
|
||||
},
|
||||
{
|
||||
input: "background-image: url(wow.gif)",
|
||||
expected: [{name: "background-image", value: "url(\"wow.gif\")", priority: ""}]
|
||||
},
|
||||
// Test that urls with :;{} characters in them are parsed correctly
|
||||
{
|
||||
input: "background: red url(\"http://site.com/image{}:;.png?id=4#wat\") repeat top right",
|
||||
expected: [
|
||||
{name: "background", value: "red url(\"http://site.com/image{}:;.png?id=4#wat\") repeat top right", priority: ""}
|
||||
]
|
||||
},
|
||||
// Test that an empty string results in an empty array
|
||||
{input: "", expected: []},
|
||||
// Test that a string comprised only of whitespaces results in an empty array
|
||||
{input: " \n \n \n \n \t \t\t\t ", expected: []},
|
||||
// Test that a null input throws an exception
|
||||
{input: null, throws: true},
|
||||
// Test that a undefined input throws an exception
|
||||
{input: undefined, throws: true},
|
||||
// Test that :;{} characters in quoted content are not parsed as multiple declarations
|
||||
{
|
||||
input: "content: \";color:red;}selector{color:yellow;\"",
|
||||
expected: [
|
||||
{name: "content", value: "\";color:red;}selector{color:yellow;\"", priority: ""}
|
||||
]
|
||||
},
|
||||
// Test that rules aren't parsed, just declarations. So { and } found after a
|
||||
// property name should be part of the property name, same for values.
|
||||
{
|
||||
input: "body {color:red;} p {color: blue;}",
|
||||
expected: [
|
||||
{name: "body {color", value: "red", priority: ""},
|
||||
{name: "} p {color", value: "blue", priority: ""},
|
||||
{name: "}", value: "", priority: ""}
|
||||
]
|
||||
},
|
||||
// Test unbalanced : and ;
|
||||
{
|
||||
input: "color :red : font : arial;",
|
||||
expected : [
|
||||
{name: "color", value: "red : font : arial", priority: ""}
|
||||
]
|
||||
},
|
||||
{input: "background: red;;;;;", expected: [{name: "background", value: "red", priority: ""}]},
|
||||
{input: "background:;", expected: [{name: "background", value: "", priority: ""}]},
|
||||
{input: ";;;;;", expected: []},
|
||||
{input: ":;:;", expected: []},
|
||||
// Test name only
|
||||
{input: "color", expected: [
|
||||
{name: "color", value: "", priority: ""}
|
||||
]},
|
||||
// Test trailing name without :
|
||||
{input: "color:blue;font", expected: [
|
||||
{name: "color", value: "blue", priority: ""},
|
||||
{name: "font", value: "", priority: ""}
|
||||
]},
|
||||
// Test trailing name with :
|
||||
{input: "color:blue;font:", expected: [
|
||||
{name: "color", value: "blue", priority: ""},
|
||||
{name: "font", value: "", priority: ""}
|
||||
]},
|
||||
// Test leading value
|
||||
{input: "Arial;color:blue;", expected: [
|
||||
{name: "", value: "Arial", priority: ""},
|
||||
{name: "color", value: "blue", priority: ""}
|
||||
]},
|
||||
// Test hex colors
|
||||
{input: "color: #333", expected: [{name: "color", value: "#333", priority: ""}]},
|
||||
{input: "color: #456789", expected: [{name: "color", value: "#456789", priority: ""}]},
|
||||
{input: "wat: #XYZ", expected: [{name: "wat", value: "#XYZ", priority: ""}]},
|
||||
// Test string/url quotes escaping
|
||||
{input: "content: \"this is a 'string'\"", expected: [{name: "content", value: "\"this is a 'string'\"", priority: ""}]},
|
||||
{input: 'content: "this is a \\"string\\""', expected: [{name: "content", value: '\'this is a "string"\'', priority: ""}]},
|
||||
{input: "content: 'this is a \"string\"'", expected: [{name: "content", value: '\'this is a "string"\'', priority: ""}]},
|
||||
{input: "content: 'this is a \\'string\\'", expected: [{name: "content", value: '"this is a \'string\'"', priority: ""}]},
|
||||
{input: "content: 'this \\' is a \" really strange string'", expected: [{name: "content", value: '"this \' is a \" really strange string"', priority: ""}]},
|
||||
{
|
||||
input: "content: \"a not s\\\
|
||||
o very long title\"",
|
||||
expected: [
|
||||
{name: "content", value: '"a not s\
|
||||
o very long title"', priority: ""}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
for (let test of TEST_DATA) {
|
||||
do_print("Test input string " + test.input);
|
||||
let output;
|
||||
try {
|
||||
output = parseDeclarations(test.input);
|
||||
} catch (e) {
|
||||
do_print("parseDeclarations threw an exception with the given input string");
|
||||
if (test.throws) {
|
||||
do_print("Exception expected");
|
||||
do_check_true(true);
|
||||
} else {
|
||||
do_print("Exception unexpected\n" + e);
|
||||
do_check_true(false);
|
||||
}
|
||||
}
|
||||
if (output) {
|
||||
assertOutput(output, test.expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertOutput(actual, expected) {
|
||||
if (actual.length === expected.length) {
|
||||
for (let i = 0; i < expected.length; i ++) {
|
||||
do_check_true(!!actual[i]);
|
||||
do_print("Check that the output item has the expected name, value and priority");
|
||||
do_check_eq(expected[i].name, actual[i].name);
|
||||
do_check_eq(expected[i].value, actual[i].value);
|
||||
do_check_eq(expected[i].priority, actual[i].priority);
|
||||
}
|
||||
} else {
|
||||
for (let prop of actual) {
|
||||
do_print("Actual output contained: {name: "+prop.name+", value: "+prop.value+", priority: "+prop.priority+"}");
|
||||
}
|
||||
do_check_eq(actual.length, expected.length);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const {parseSingleValue} = devtools.require("devtools/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
{input: null, throws: true},
|
||||
{input: undefined, throws: true},
|
||||
{input: "", expected: {value: "", priority: ""}},
|
||||
{input: " \t \t \n\n ", expected: {value: "", priority: ""}},
|
||||
{input: "blue", expected: {value: "blue", priority: ""}},
|
||||
{input: "blue !important", expected: {value: "blue", priority: "important"}},
|
||||
{input: "blue!important", expected: {value: "blue", priority: "important"}},
|
||||
{input: "blue ! important", expected: {value: "blue", priority: "important"}},
|
||||
{input: "blue ! important", expected: {value: "blue", priority: "important"}},
|
||||
{input: "blue !", expected: {value: "blue", priority: ""}},
|
||||
{input: "blue !mportant", expected: {value: "blue !mportant", priority: ""}},
|
||||
{input: " blue !important ", expected: {value: "blue", priority: "important"}},
|
||||
{
|
||||
input: "url(\"http://url.com/whyWouldYouDoThat!important.png\") !important",
|
||||
expected: {
|
||||
value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
|
||||
priority: "important"
|
||||
}
|
||||
},
|
||||
{
|
||||
input: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
|
||||
expected: {
|
||||
value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
|
||||
priority: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
input: "\"content!important\" !important",
|
||||
expected: {
|
||||
value: "\"content!important\"",
|
||||
priority: "important"
|
||||
}
|
||||
},
|
||||
{
|
||||
input: "\"content!important\"",
|
||||
expected: {
|
||||
value: "\"content!important\"",
|
||||
priority: ""
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
for (let test of TEST_DATA) {
|
||||
do_print("Test input value " + test.input);
|
||||
try {
|
||||
let output = parseSingleValue(test.input);
|
||||
assertOutput(output, test.expected);
|
||||
} catch (e) {
|
||||
do_print("parseSingleValue threw an exception with the given input value");
|
||||
if (test.throws) {
|
||||
do_print("Exception expected");
|
||||
do_check_true(true);
|
||||
} else {
|
||||
do_print("Exception unexpected\n" + e);
|
||||
do_check_true(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertOutput(actual, expected) {
|
||||
do_print("Check that the output has the expected value and priority");
|
||||
do_check_eq(expected.value, actual.value);
|
||||
do_check_eq(expected.priority, actual.priority);
|
||||
}
|
7
browser/devtools/styleinspector/test/unit/xpcshell.ini
Normal file
7
browser/devtools/styleinspector/test/unit/xpcshell.ini
Normal file
@ -0,0 +1,7 @@
|
||||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
|
||||
[test_parseDeclarations.js]
|
||||
[test_parseSingleValue.js]
|
Loading…
Reference in New Issue
Block a user