/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   emk <VYV03354@nifty.ne.jp>
 *   Daniel Glazman <glazman@netscape.com>
 *   L. David Baron <dbaron@dbaron.org>
 *   Boris Zbarsky <bzbarsky@mit.edu>
 *   Mats Palmgren <mats.palmgren@bredband.net>
 *   Christian Biesinger <cbiesinger@web.de>
 *   Jeff Walden <jwalden+code@mit.edu>
 *   Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>, Collabora Ltd.
 *   Siraj Razick <siraj.razick@collabora.co.uk>, Collabora Ltd.
 *   Daniel Glazman <daniel.glazman@disruptive-innovations.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var kCHARSET_RULE_MISSING_SEMICOLON = "Missing semicolon at the end of @charset rule";
var kCHARSET_RULE_CHARSET_IS_STRING = "The charset in the @charset rule should be a string";
var kCHARSET_RULE_MISSING_WS = "Missing mandatory whitespace after @charset";
var kIMPORT_RULE_MISSING_URL = "Missing URL in @import rule";
var kURL_EOF = "Unexpected end of stylesheet";
var kURL_WS_INSIDE = "Multiple tokens inside a url() notation";
var kVARIABLES_RULE_POSITION = "@variables rule invalid at this position in the stylesheet";
var kIMPORT_RULE_POSITION = "@import rule invalid at this position in the stylesheet";
var kNAMESPACE_RULE_POSITION = "@namespace rule invalid at this position in the stylesheet";
var kCHARSET_RULE_CHARSET_SOF = "@charset rule invalid at this position in the stylesheet";
var kUNKNOWN_AT_RULE = "Unknow @-rule";

/* FROM http://peter.sh/data/vendor-prefixed-css.php?js=1 */

var kCSS_VENDOR_VALUES = {
  "-moz-box":             {"webkit": "-webkit-box",        "presto": "", "trident": "", "generic": "box" },
  "-moz-inline-box":      {"webkit": "-webkit-inline-box", "presto": "", "trident": "", "generic": "inline-box" },
  "-moz-initial":         {"webkit": "",                   "presto": "", "trident": "", "generic": "initial" },
  "flex":                 {"webkit": "-webkit-flex",       "presto": "", "trident": "", "generic": "" },
  "inline-flex":          {"webkit": "-webkit-inline-flex", "presto": "", "trident": "", "generic": "" },

  "linear-gradient": {"webkit20110101":FilterLinearGradient,
                           "webkit": FilterLinearGradient,
                           "presto": FilterLinearGradient,
                           "trident": FilterLinearGradient,
                           "gecko1.9.2": FilterLinearGradient },
  "repeating-linear-gradient": {"webkit20110101":FilterLinearGradient,
                           "webkit": FilterLinearGradient,
                           "presto": FilterLinearGradient,
                           "trident": FilterLinearGradient,
                           "gecko1.9.2": FilterLinearGradient },

  "radial-gradient": {"webkit20110101":FilterRadialGradient,
                           "webkit": FilterRadialGradient,
                           "presto": FilterRadialGradient,
                           "trident": FilterRadialGradient,
                           "gecko1.9.2": FilterRadialGradient },
  "repeating-radial-gradient": {"webkit20110101":FilterRadialGradient,
                           "webkit": FilterRadialGradient,
                           "presto": FilterRadialGradient,
                           "trident": FilterRadialGradient,
                           "gecko1.9.2": FilterRadialGradient }
};

var kCSS_PREFIXED_VALUE = [
  {"gecko": "-moz-box", "webkit": "-moz-box", "presto": "", "trident": "", "generic": "box"}
];

var kCSS_VENDOR_PREFIXES = 
{"lastUpdate":1374677405,"properties":[
{"gecko":"","webkit":"","presto":"","trident":"-ms-accelerator","status":"P"},
{"gecko":"","webkit":"","presto":"-wap-accesskey","trident":"","status":""},
{"gecko":"","webkit":"-webkit-align-content","presto":"","trident":"","status":""},
{"gecko":"align-items","webkit":"-webkit-align-items","presto":"","trident":"","status":""},
{"gecko":"align-self","webkit":"-webkit-align-self","presto":"","trident":"","status":""},
{"gecko":"animation","webkit":"-webkit-animation","presto":"","trident":"animation","status":"WD"},
{"gecko":"animation-delay","webkit":"-webkit-animation-delay","presto":"","trident":"animation-delay","status":"WD"},
{"gecko":"animation-direction","webkit":"-webkit-animation-direction","presto":"","trident":"animation-direction","status":"WD"},
{"gecko":"animation-duration","webkit":"-webkit-animation-duration","presto":"","trident":"animation-duration","status":"WD"},
{"gecko":"animation-fill-mode","webkit":"-webkit-animation-fill-mode","presto":"","trident":"animation-fill-mode","status":"ED"},
{"gecko":"animation-iteration-count","webkit":"-webkit-animation-iteration-count","presto":"","trident":"animation-iteration-count","status":"WD"},
{"gecko":"animation-name","webkit":"-webkit-animation-name","presto":"","trident":"animation-name","status":"WD"},
{"gecko":"animation-play-state","webkit":"-webkit-animation-play-state","presto":"","trident":"animation-play-state","status":"WD"},
{"gecko":"animation-timing-function","webkit":"-webkit-animation-timing-function","presto":"","trident":"animation-timing-function","status":"WD"},
{"gecko":"","webkit":"-webkit-app-region","presto":"","trident":"","status":""},
{"gecko":"-moz-appearance","webkit":"-webkit-appearance","presto":"","trident":"","status":"CR"},
{"gecko":"","webkit":"-webkit-aspect-ratio","presto":"","trident":"","status":""},
{"gecko":"backface-visibility","webkit":"-webkit-backface-visibility","presto":"","trident":"backface-visibility","status":"WD"},
{"gecko":"","webkit":"-webkit-background-blend-mode","presto":"","trident":"","status":""},
{"gecko":"background-clip","webkit":"-webkit-background-clip","presto":"background-clip","trident":"background-clip","status":"WD"},
{"gecko":"","webkit":"-webkit-background-composite","presto":"","trident":"","status":""},
{"gecko":"-moz-background-inline-policy","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"background-origin","webkit":"-webkit-background-origin","presto":"background-origin","trident":"background-origin","status":"WD"},
{"gecko":"","webkit":"background-position-x","presto":"","trident":"-ms-background-position-x","status":""},
{"gecko":"","webkit":"background-position-y","presto":"","trident":"-ms-background-position-y","status":""},
{"gecko":"background-size","webkit":"-webkit-background-size","presto":"background-size","trident":"background-size","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-behavior","status":""},
{"gecko":"-moz-binding","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-blend-mode","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-block-progression","status":""},
{"gecko":"","webkit":"-webkit-border-after","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-after-color","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-after-style","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-after-width","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-before","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-before-color","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-before-style","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-before-width","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-bottom-colors","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"border-bottom-left-radius","webkit":"-webkit-border-bottom-left-radius","presto":"border-bottom-left-radius","trident":"border-bottom-left-radius","status":"WD"},
{"gecko":"border-bottom-right-radius","webkit":"-webkit-border-bottom-right-radius","presto":"border-bottom-right-radius","trident":"border-bottom-right-radius","status":"WD"},
{"gecko":"-moz-border-end","webkit":"-webkit-border-end","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-end-color","webkit":"-webkit-border-end-color","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-end-style","webkit":"-webkit-border-end-style","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-end-width","webkit":"-webkit-border-end-width","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-border-fit","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-border-horizontal-spacing","presto":"","trident":"","status":""},
{"gecko":"border-image","webkit":"-webkit-border-image","presto":"-o-border-image","trident":"","status":"WD"},
{"gecko":"-moz-border-left-colors","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"border-radius","webkit":"-webkit-border-radius","presto":"border-radius","trident":"border-radius","status":"WD"},
{"gecko":"-moz-border-right-colors","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"-moz-border-start","webkit":"-webkit-border-start","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-start-color","webkit":"-webkit-border-start-color","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-start-style","webkit":"-webkit-border-start-style","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-start-width","webkit":"-webkit-border-start-width","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-border-top-colors","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"border-top-left-radius","webkit":"-webkit-border-top-left-radius","presto":"border-top-left-radius","trident":"border-top-left-radius","status":"WD"},
{"gecko":"border-top-right-radius","webkit":"-webkit-border-top-right-radius","presto":"border-top-right-radius","trident":"border-top-right-radius","status":"WD"},
{"gecko":"","webkit":"-webkit-border-vertical-spacing","presto":"","trident":"","status":""},
{"gecko":"-moz-box-align","webkit":"-webkit-box-align","presto":"","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-box-decoration-break","presto":"box-decoration-break","trident":"","status":"WD"},
{"gecko":"-moz-box-direction","webkit":"-webkit-box-direction","presto":"","trident":"","status":"WD"},
{"gecko":"-moz-box-flex","webkit":"-webkit-box-flex","presto":"","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-box-flex-group","presto":"","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-box-lines","presto":"","trident":"","status":"WD"},
{"gecko":"-moz-box-ordinal-group","webkit":"-webkit-box-ordinal-group","presto":"","trident":"","status":"WD"},
{"gecko":"-moz-box-orient","webkit":"-webkit-box-orient","presto":"","trident":"","status":"WD"},
{"gecko":"-moz-box-pack","webkit":"-webkit-box-pack","presto":"","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-box-reflect","presto":"","trident":"","status":""},
{"gecko":"box-shadow","webkit":"-webkit-box-shadow","presto":"box-shadow","trident":"box-shadow","status":"WD"},
{"gecko":"-moz-box-sizing","webkit":"-webkit-box-sizing","presto":"box-sizing","trident":"box-sizing","status":"CR"},
{"gecko":"caption-side","webkit":"-epub-caption-side","presto":"caption-side","trident":"caption-side","status":""},
{"gecko":"clip-path","webkit":"-webkit-clip-path","presto":"clip-path","trident":"","status":""},
{"gecko":"","webkit":"-webkit-color-correction","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-column-axis","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-column-break-after","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-column-break-before","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-column-break-inside","presto":"","trident":"","status":""},
{"gecko":"-moz-column-count","webkit":"-webkit-column-count","presto":"column-count","trident":"column-count","status":"CR"},
{"gecko":"-moz-column-fill","webkit":"","presto":"column-fill","trident":"column-fill","status":"CR"},
{"gecko":"-moz-column-gap","webkit":"-webkit-column-gap","presto":"column-gap","trident":"column-gap","status":"CR"},
{"gecko":"","webkit":"-webkit-column-progression","presto":"","trident":"","status":""},
{"gecko":"-moz-column-rule","webkit":"-webkit-column-rule","presto":"column-rule","trident":"column-rule","status":"CR"},
{"gecko":"-moz-column-rule-color","webkit":"-webkit-column-rule-color","presto":"column-rule-color","trident":"column-rule-color","status":"CR"},
{"gecko":"-moz-column-rule-style","webkit":"-webkit-column-rule-style","presto":"column-rule-style","trident":"column-rule-style","status":"CR"},
{"gecko":"-moz-column-rule-width","webkit":"-webkit-column-rule-width","presto":"column-rule-width","trident":"column-rule-width","status":"CR"},
{"gecko":"","webkit":"-webkit-column-span","presto":"column-span","trident":"column-span","status":"CR"},
{"gecko":"-moz-column-width","webkit":"-webkit-column-width","presto":"column-width","trident":"column-width","status":"CR"},
{"gecko":"-moz-columns","webkit":"-webkit-columns","presto":"columns","trident":"columns","status":"CR"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-chaining","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit-max","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit-min","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap-points","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap-type","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zooming","status":""},
{"gecko":"","webkit":"-webkit-cursor-visibility","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-dashboard-region","presto":"-apple-dashboard-region","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-o-device-pixel-ratio","trident":"","status":""},
{"gecko":"filter","webkit":"-webkit-filter","presto":"filter","trident":"-ms-filter","status":""},
{"gecko":"flex","webkit":"-webkit-flex","presto":"","trident":"-ms-flex","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-align","status":""},
{"gecko":"flex-basis","webkit":"-webkit-flex-basis","presto":"","trident":"","status":""},
{"gecko":"flex-direction","webkit":"-webkit-flex-direction","presto":"","trident":"-ms-flex-direction","status":""},
{"gecko":"","webkit":"-webkit-flex-flow","presto":"","trident":"","status":""},
{"gecko":"flex-grow","webkit":"-webkit-flex-grow","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-order","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-pack","status":""},
{"gecko":"flex-shrink","webkit":"-webkit-flex-shrink","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-flex-wrap","presto":"","trident":"-ms-flex-wrap","status":""},
{"gecko":"-moz-float-edge","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-flow-from","presto":"","trident":"-ms-flow-from","status":""},
{"gecko":"","webkit":"-webkit-flow-into","presto":"","trident":"-ms-flow-into","status":""},
{"gecko":"","webkit":"","presto":"-o-focus-opacity","trident":"","status":""},
{"gecko":"-moz-font-feature-settings","webkit":"-webkit-font-feature-settings","presto":"","trident":"font-feature-settings","status":""},
{"gecko":"font-kerning","webkit":"-webkit-font-kerning","presto":"","trident":"","status":""},
{"gecko":"-moz-font-language-override","webkit":"","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-font-size-delta","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-font-smoothing","presto":"","trident":"","status":""},
{"gecko":"font-variant-ligatures","webkit":"-webkit-font-variant-ligatures","presto":"","trident":"","status":""},
{"gecko":"-moz-force-broken-image-icon","webkit":"","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-after","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-auto-columns","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-auto-flow","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-auto-rows","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-before","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-column","presto":"","trident":"-ms-grid-column","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-column-align","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-column-span","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-columns","status":"WD"},
{"gecko":"","webkit":"-webkit-grid-definition-columns","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-definition-rows","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-end","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-grid-row","presto":"","trident":"-ms-grid-row","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-row-align","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-row-span","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-rows","status":"WD"},
{"gecko":"","webkit":"-webkit-grid-start","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-high-contrast-adjust","status":""},
{"gecko":"","webkit":"-webkit-highlight","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-hyphenate-character","presto":"","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-hyphenate-limit-after","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-hyphenate-limit-before","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-hyphenate-limit-chars","status":""},
{"gecko":"","webkit":"-webkit-hyphenate-limit-lines","presto":"","trident":"-ms-hyphenate-limit-lines","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-hyphenate-limit-zone","status":""},
{"gecko":"-moz-hyphens","webkit":"-epub-hyphens","presto":"","trident":"-ms-hyphens","status":"WD"},
{"gecko":"-moz-image-region","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"ime-mode","webkit":"","presto":"","trident":"-ms-ime-mode","status":""},
{"gecko":"","webkit":"","presto":"-wap-input-format","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-wap-input-required","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-interpolation-mode","status":""},
{"gecko":"","webkit":"","presto":"-xv-interpret-as","trident":"","status":""},
{"gecko":"justify-content","webkit":"-webkit-justify-content","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-flow","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-char","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-line","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-mode","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-type","status":""},
{"gecko":"","webkit":"-webkit-line-align","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-line-box-contain","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-line-break","presto":"","trident":"line-break","status":""},
{"gecko":"","webkit":"-webkit-line-clamp","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-line-grid","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-line-snap","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-o-link","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-o-link-source","trident":"","status":""},
{"gecko":"","webkit":"-webkit-locale","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-logical-height","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-logical-width","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-margin-after","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-margin-after-collapse","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-margin-before","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-margin-before-collapse","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-margin-bottom-collapse","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-margin-collapse","presto":"","trident":"","status":""},
{"gecko":"-moz-margin-end","webkit":"-webkit-margin-end","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-margin-start","webkit":"-webkit-margin-start","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-margin-top-collapse","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-marquee","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-wap-marquee-dir","trident":"","status":""},
{"gecko":"","webkit":"-webkit-marquee-direction","presto":"","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-marquee-increment","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-wap-marquee-loop","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-marquee-repetition","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-marquee-speed","presto":"-wap-marquee-speed","trident":"","status":"WD"},
{"gecko":"","webkit":"-webkit-marquee-style","presto":"-wap-marquee-style","trident":"","status":"WD"},
{"gecko":"mask","webkit":"-webkit-mask","presto":"mask","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-box-image","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-box-image-outset","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-box-image-repeat","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-box-image-slice","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-box-image-source","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-box-image-width","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-clip","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-composite","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-image","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-origin","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-position","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-position-x","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-position-y","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-repeat","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-repeat-x","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-repeat-y","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-mask-size","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-max-logical-height","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-max-logical-width","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-min-logical-height","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-min-logical-width","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"","presto":"-o-mini-fold","trident":"","status":""},
{"gecko":"","webkit":"-webkit-nbsp-mode","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"","presto":"-o-object-fit","trident":"","status":"ED"},
{"gecko":"","webkit":"","presto":"-o-object-position","trident":"","status":"ED"},
{"gecko":"opacity","webkit":"-webkit-opacity","presto":"opacity","trident":"opacity","status":"WD"},
{"gecko":"order","webkit":"-webkit-order","presto":"","trident":"","status":""},
{"gecko":"-moz-orient","webkit":"","presto":"","trident":"","status":""},
{"gecko":"-moz-outline-radius","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"-moz-outline-radius-bottomleft","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"-moz-outline-radius-bottomright","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"-moz-outline-radius-topleft","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"-moz-outline-radius-topright","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-overflow-scrolling","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-overflow-style","status":"CR"},
{"gecko":"overflow-x","webkit":"overflow-x","presto":"overflow-x","trident":"-ms-overflow-x","status":"WD"},
{"gecko":"overflow-y","webkit":"overflow-y","presto":"overflow-y","trident":"-ms-overflow-y","status":"WD"},
{"gecko":"","webkit":"-webkit-padding-after","presto":"","trident":"","status":"ED"},
{"gecko":"","webkit":"-webkit-padding-before","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-padding-end","webkit":"-webkit-padding-end","presto":"","trident":"","status":"ED"},
{"gecko":"-moz-padding-start","webkit":"-webkit-padding-start","presto":"","trident":"","status":"ED"},
{"gecko":"perspective","webkit":"-webkit-perspective","presto":"","trident":"perspective","status":"WD"},
{"gecko":"perspective-origin","webkit":"-webkit-perspective-origin","presto":"","trident":"perspective-origin","status":"WD"},
{"gecko":"","webkit":"-webkit-perspective-origin-x","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-perspective-origin-y","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-xv-phonemes","trident":"","status":""},
{"gecko":"","webkit":"-webkit-print-color-adjust","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-progress-appearance","status":""},
{"gecko":"","webkit":"-webkit-region-break-after","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-region-break-before","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-region-break-inside","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-region-fragment","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-rtl-ordering","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-ruby-position","presto":"","trident":"ruby-position","status":"CR"},
{"gecko":"-moz-script-level","webkit":"","presto":"","trident":"","status":""},
{"gecko":"-moz-script-min-size","webkit":"","presto":"","trident":"","status":""},
{"gecko":"-moz-script-size-multiplier","webkit":"","presto":"","trident":"","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-chaining","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-x-max","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-x-min","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-y-max","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-y-min","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-rails","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-points-x","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-points-y","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-type","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-x","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-y","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-translation","status":""},
{"gecko":"","webkit":"","presto":"scrollbar-arrow-color","trident":"-ms-scrollbar-arrow-color","status":"P"},
{"gecko":"","webkit":"","presto":"scrollbar-base-color","trident":"-ms-scrollbar-base-color","status":"P"},
{"gecko":"","webkit":"","presto":"scrollbar-darkshadow-color","trident":"-ms-scrollbar-darkshadow-color","status":"P"},
{"gecko":"","webkit":"","presto":"scrollbar-face-color","trident":"-ms-scrollbar-face-color","status":"P"},
{"gecko":"","webkit":"","presto":"scrollbar-highlight-color","trident":"-ms-scrollbar-highlight-color","status":"P"},
{"gecko":"","webkit":"","presto":"scrollbar-shadow-color","trident":"-ms-scrollbar-shadow-color","status":"P"},
{"gecko":"","webkit":"","presto":"scrollbar-track-color","trident":"-ms-scrollbar-track-color","status":"P"},
{"gecko":"","webkit":"-webkit-shape-inside","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-shape-margin","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-shape-outside","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-shape-padding","presto":"","trident":"","status":""},
{"gecko":"-moz-stack-sizing","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-svg-shadow","presto":"","trident":"","status":""},
{"gecko":"-moz-tab-size","webkit":"tab-size","presto":"-o-tab-size","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-o-table-baseline","trident":"","status":""},
{"gecko":"","webkit":"-webkit-tap-highlight-color","presto":"","trident":"","status":"P"},
{"gecko":"-moz-text-align-last","webkit":"-webkit-text-align-last","presto":"","trident":"-ms-text-align-last","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-text-autospace","status":"WD"},
{"gecko":"-moz-text-blink","webkit":"","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-epub-text-combine","presto":"","trident":"","status":""},
{"gecko":"-moz-text-decoration-color","webkit":"-webkit-text-decoration-color","presto":"","trident":"","status":""},
{"gecko":"-moz-text-decoration-line","webkit":"-webkit-text-decoration-line","presto":"","trident":"","status":""},
{"gecko":"-moz-text-decoration-style","webkit":"-webkit-text-decoration-style","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-text-decorations-in-effect","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-epub-text-emphasis","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-epub-text-emphasis-color","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-text-emphasis-position","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-epub-text-emphasis-style","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-text-fill-color","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-text-justify","presto":"","trident":"-ms-text-justify","status":"WD"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-text-kashida-space","status":"P"},
{"gecko":"","webkit":"-epub-text-orientation","presto":"","trident":"","status":""},
{"gecko":"text-overflow","webkit":"text-overflow","presto":"text-overflow","trident":"-ms-text-overflow","status":"WD"},
{"gecko":"","webkit":"-webkit-text-security","presto":"","trident":"","status":"P"},
{"gecko":"-moz-text-size-adjust","webkit":"","presto":"","trident":"","status":""},
{"gecko":"","webkit":"-webkit-text-stroke","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-text-stroke-color","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-text-stroke-width","presto":"","trident":"","status":"P"},
{"gecko":"text-transform","webkit":"-epub-text-transform","presto":"text-transform","trident":"text-transform","status":""},
{"gecko":"","webkit":"-webkit-text-underline-position","presto":"","trident":"-ms-text-underline-position","status":"P"},
{"gecko":"","webkit":"","presto":"","trident":"-ms-touch-action","status":""},
{"gecko":"","webkit":"-webkit-touch-callout","presto":"","trident":"","status":"P"},
{"gecko":"-moz-transform","webkit":"-webkit-transform","presto":"-o-transform","trident":"transform","status":"WD"},
{"gecko":"transform-origin","webkit":"-webkit-transform-origin","presto":"-o-transform-origin","trident":"transform-origin","status":"WD"},
{"gecko":"","webkit":"-webkit-transform-origin-x","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-transform-origin-y","presto":"","trident":"","status":"P"},
{"gecko":"","webkit":"-webkit-transform-origin-z","presto":"","trident":"","status":"P"},
{"gecko":"transform-style","webkit":"-webkit-transform-style","presto":"","trident":"transform-style","status":"WD"},
{"gecko":"transition","webkit":"-webkit-transition","presto":"-o-transition","trident":"transition","status":"WD"},
{"gecko":"transition-delay","webkit":"-webkit-transition-delay","presto":"-o-transition-delay","trident":"transition-delay","status":"WD"},
{"gecko":"transition-duration","webkit":"-webkit-transition-duration","presto":"-o-transition-duration","trident":"transition-duration","status":"WD"},
{"gecko":"transition-property","webkit":"-webkit-transition-property","presto":"-o-transition-property","trident":"transition-property","status":"WD"},
{"gecko":"transition-timing-function","webkit":"-webkit-transition-timing-function","presto":"-o-transition-timing-function","trident":"transition-timing-function","status":"WD"},
{"gecko":"","webkit":"-webkit-user-drag","presto":"","trident":"","status":"P"},
{"gecko":"-moz-user-focus","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"-moz-user-input","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"-moz-user-modify","webkit":"-webkit-user-modify","presto":"","trident":"","status":"P"},
{"gecko":"-moz-user-select","webkit":"-webkit-user-select","presto":"","trident":"-ms-user-select","status":"P"},
{"gecko":"","webkit":"","presto":"-xv-voice-balance","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-xv-voice-duration","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-xv-voice-pitch","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-xv-voice-pitch-range","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-xv-voice-rate","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-xv-voice-stress","trident":"","status":""},
{"gecko":"","webkit":"","presto":"-xv-voice-volume","trident":"","status":""},
{"gecko":"-moz-window-shadow","webkit":"","presto":"","trident":"","status":"P"},
{"gecko":"word-break","webkit":"-epub-word-break","presto":"","trident":"-ms-word-break","status":"WD"},
{"gecko":"word-wrap","webkit":"word-wrap","presto":"word-wrap","trident":"-ms-word-wrap","status":"WD"},
{"gecko":"","webkit":"-webkit-wrap-flow","presto":"","trident":"-ms-wrap-flow","status":""},
{"gecko":"","webkit":"","presto":"","trident":"-ms-wrap-margin","status":""},
{"gecko":"","webkit":"-webkit-wrap-through","presto":"","trident":"-ms-wrap-through","status":""},
{"gecko":"writing-mode","webkit":"-epub-writing-mode","presto":"writing-mode","trident":"-ms-writing-mode","status":"ED"},
{"gecko":"","webkit":"zoom","presto":"","trident":"-ms-zoom","status":""}]};

var PrefixHelper = {

  mVENDOR_PREFIXES: null,

  kEXPORTS_FOR_GECKO:   true,
  kEXPORTS_FOR_WEBKIT:  true,
  kEXPORTS_FOR_PRESTO:  true,
  kEXPORTS_FOR_TRIDENT: true,

  cleanPrefixes: function()
  {
    this.mVENDOR_PREFIXES = null;
  },

  prefixesForProperty: function(aProperty)
  {
    if (!this.mVENDOR_PREFIXES) {

      this.mVENDOR_PREFIXES = {};
      for (var i = 0; i < kCSS_VENDOR_PREFIXES.properties.length; i++) {
        var p = kCSS_VENDOR_PREFIXES.properties[i];
        if (p.gecko && (p.webkit || p.presto || p.trident)) {
          var o = {};
          if (this.kEXPORTS_FOR_GECKO) o[p.gecko] = true;
          if (this.kEXPORTS_FOR_WEBKIT && p.webkit)  o[p.webkit] = true;
          if (this.kEXPORTS_FOR_PRESTO && p.presto)  o[p.presto] = true;
          if (this.kEXPORTS_FOR_TRIDENT && p.trident) o[p.trident] = true;
          this.mVENDOR_PREFIXES[p.gecko] = [];
          for (var j in o)
            this.mVENDOR_PREFIXES[p.gecko].push(j)
        }
      }
    }
    if (aProperty in this.mVENDOR_PREFIXES)
      return this.mVENDOR_PREFIXES[aProperty].sort();
    return null;
  }
};

function ParseURL(buffer) {
  var result = { };
  result.protocol = "";
  result.user = "";
  result.password = "";
  result.host = "";
  result.port = "";
  result.path = "";
  result.query = "";

  var section = "PROTOCOL";
  var start = 0;
  var wasSlash = false;

  while(start < buffer.length) {
    if(section == "PROTOCOL") {
      if(buffer.charAt(start) == ':') {
        section = "AFTER_PROTOCOL";
        start++;
      } else if(buffer.charAt(start) == '/' && result.protocol.length() == 0) { 
        section = PATH;
      } else {
        result.protocol += buffer.charAt(start++);
      }
    } else if(section == "AFTER_PROTOCOL") {
      if(buffer.charAt(start) == '/') {
    if(!wasSlash) {
          wasSlash = true;
    } else {
          wasSlash = false;
          section = "USER";
    }
        start ++;
      } else {
        throw new ParseException("Protocol shell be separated with 2 slashes");
      }       
    } else if(section == "USER") {
      if(buffer.charAt(start) == '/') {
        result.host = result.user;
        result.user = "";
        section = "PATH";
      } else if(buffer.charAt(start) == '?') {
        result.host = result.user;
        result.user = "";
        section = "QUERY";
        start++;
      } else if(buffer.charAt(start) == ':') {
        section = "PASSWORD";
        start++;
      } else if(buffer.charAt(start) == '@') {
        section = "HOST";
        start++;
      } else {
        result.user += buffer.charAt(start++);
      }
    } else if(section == "PASSWORD") {
      if(buffer.charAt(start) == '/') {
        result.host = result.user;
        result.port = result.password;
        result.user = "";
        result.password = "";
        section = "PATH";
      } else if(buffer.charAt(start) == '?') {
        result.host = result.user;
        result.port = result.password;
        result.user = "";
        result.password = "";
        section = "QUERY";
        start ++;
      } else if(buffer.charAt(start) == '@') {
        section = "HOST";
        start++;
      } else {
        result.password += buffer.charAt(start++);
      }
    } else if(section == "HOST") {
      if(buffer.charAt(start) == '/') {
        section = "PATH";
      } else if(buffer.charAt(start) == ':') {
        section = "PORT";
        start++;
      } else if(buffer.charAt(start) == '?') {
        section = "QUERY";
        start++;
      } else {
        result.host += buffer.charAt(start++);
      }
    } else if(section == "PORT") {
      if(buffer.charAt(start) == '/') {
        section = "PATH";
      } else if(buffer.charAt(start) == '?') {
        section = "QUERY";
        start++;
      } else {
        result.port += buffer.charAt(start++);
      }
    } else if(section == "PATH") {
      if(buffer.charAt(start) == '?') {
    section = "QUERY";
    start ++;
      } else {
    result.path += buffer.charAt(start++);
      }
    } else if(section == "QUERY") {
      result.query += buffer.charAt(start++);
    }
  }

  if(section == "PROTOCOL") {
    result.host = result.protocol;
    result.protocol = "http";
  } else if(section == "AFTER_PROTOCOL") {
    throw new ParseException("Invalid url");
  } else if(section == "USER") {
    result.host = result.user;
    result.user = "";
  } else if(section == "PASSWORD") {
    result.host = result.user;
    result.port = result.password;
    result.user = "";
    result.password = "";
  }

  return result;
}

function ParseException(description) {
    this.description = description;
}

function CountLF(s)
{
  var nCR = s.match( /\n/g );
  return nCR ? nCR.length + 1 : 1;
}

function DisposablePartialParsing(aStringToParse, aMethodName)
{
  var parser = new CSSParser();
  parser._init();
  parser.mPreserveWS       = false;
  parser.mPreserveComments = false;
  parser.mPreservedTokens = [];
  parser.mScanner.init(aStringToParse);

  return parser[aMethodName]();    
}

function FilterLinearGradient(aValue, aEngine)
{
  var d = DisposablePartialParsing(aValue, "parseBackgroundImages");
  if (!d)
    return null;
  var g = d[0];
  if (!g.value)
    return null;

  var str = "";
  var position = ("position" in g.value) ? g.value.position.toLowerCase() : "";
  var angle    = ("angle" in g.value) ? g.value.angle.toLowerCase() : "";

  if ("webkit20110101" == aEngine) {
    var cancelled = false;
    str = "-webkit-gradient(linear, ";
    // normalize angle
    if (angle) {
      var match = angle.match(/^([0-9\-\.\\+]+)([a-z]*)/);
      var angle = parseFloat(match[1]);
      var unit  = match[2];
      switch (unit) {
        case "grad": angle = angle * 90 / 100; break;
        case "rad":  angle = angle * 180 / Math.PI; break;
        default: break;
      }
      while (angle < 0)
        angle += 360;
      while (angle >= 360)
        angle -= 360;
    }
    // get startpoint w/o keywords
    var startpoint = [];
    var endpoint = [];
    if (position != "") {
      if (position == "center")
        position = "center center";
      startpoint = position.split(" ");
      if (angle == "" && angle != 0) {
        // no angle, then we just turn the point 180 degrees around center
        switch (startpoint[0]) {
          case "left":   endpoint.push("right"); break;
          case "center": endpoint.push("center"); break;
          case "right":  endpoint.push("left"); break;
          default: {
              var match = startpoint[0].match(/^([0-9\-\.\\+]+)([a-z]*)/);
              var v     = parseFloat(match[0]);
              var unit  = match[1];
              if (unit == "%") {
                endpoint.push((100-v) + "%");
              }
              else
                cancelled = true;
            }
            break;
        }
        if (!cancelled)
          switch (startpoint[1]) {
            case "top":    endpoint.push("bottom"); break;
            case "center": endpoint.push("center"); break;
            case "bottom": endpoint.push("top"); break;
            default: {
                var match = startpoint[1].match(/^([0-9\-\.\\+]+)([a-z]*)/);
                var v     = parseFloat(match[0]);
                var unit  = match[1];
                if (unit == "%") {
                  endpoint.push((100-v) + "%");
                }
                else
                  cancelled = true;
              }
              break;
          }
      }
      else {
        switch (angle) {
          case 0:    endpoint.push("right"); endpoint.push(startpoint[1]); break;
          case 90:   endpoint.push(startpoint[0]); endpoint.push("top"); break;
          case 180:  endpoint.push("left"); endpoint.push(startpoint[1]); break;
          case 270:  endpoint.push(startpoint[0]); endpoint.push("bottom"); break;
          default:     cancelled = true; break;
        }
      }
    }
    else {
      // no position defined, we accept only vertical and horizontal
      if (angle == "")
        angle = 270;
      switch (angle) {
        case 0:    startpoint= ["left", "center"];   endpoint = ["right", "center"]; break;
        case 90:   startpoint= ["center", "bottom"]; endpoint = ["center", "top"]; break;
        case 180:  startpoint= ["right", "center"];  endpoint = ["left", "center"]; break;
        case 270:  startpoint= ["center", "top"];    endpoint = ["center", "bottom"]; break;
        default:     cancelled = true; break;
      }
    }
  
    if (cancelled)
      return "";
  
    str += startpoint.join(" ") + ", " + endpoint.join(" ");
    if (!g.value.stops[0].position)
      g.value.stops[0].position = "0%";
    if (!g.value.stops[g.value.stops.length-1].position)
      g.value.stops[g.value.stops.length-1].position = "100%";
    var current = 0;
    for (var i = 0; i < g.value.stops.length && !cancelled; i++) {
      var s = g.value.stops[i];
      if (s.position) {
        if (s.position.indexOf("%") == -1) {
          cancelled = true;
          break;
        }
      }
      else {
        var j = i + 1;
        while (j < g.value.stops.length && !g.value.stops[j].position)
          j++;
        var inc = parseFloat(g.value.stops[j].position) - current;
        for (var k = i; k < j; k++) {
          g.value.stops[k].position = (current + inc * (k - i + 1) / (j - i + 1)) + "%";
        }
      }
      current = parseFloat(s.position);
      str += ", color-stop(" + (parseFloat(current) / 100) + ", " + s.color + ")";
    }
  
    if (cancelled)
      return "";
  }
  else {
    str = (g.value.isRepeating ? "repeating-" : "") + "linear-gradient(";
    if (angle || position)
      str += (angle ? angle : position) + ", ";
  
    for (var i = 0; i < g.value.stops.length; i++) {
      var s = g.value.stops[i];
      str += s.color
             + (s.position ? " " + s.position : "")
             + ((i != g.value.stops.length -1) ? ", " : "");
    }
  }
  str += ")";

  switch (aEngine) {
    case "webkit":     str = "-webkit-"  + str; break;
    case "gecko1.9.2": str = "-moz-"  + str; break;
    case "presto":     str = "-o-"  + str; break;
    case "trident":    str = "-ms-"  + str; break;
    default:           break;
  }
  return str;
}

function FilterRadialGradient(aValue, aEngine)
{
  var d = DisposablePartialParsing(aValue, "parseBackgroundImages");
  if (!d)
    return null;
  var g = d[0];
  if (!g.value)
    return null;

  // oh come on, this is now so painful to deal with ; no way I'm going to implement this
  if ("webkit20110101" == aEngine)
    return null;
  
  var str = (g.value.isRepeating ? "repeating-" : "") + "radial-gradient(";
  var shape = ("shape" in g.value) ? g.value.shape : "";
  var extent  = ("extent"  in g.value) ? g.value.extent : "";
  var lengths = "";
  switch (g.value.positions.length) {
    case 1:
      lengths = g.value.positions[0] + " " + g.value.positions[0];
      break;
    case 2:
      lengths = g.value.positions[0] + " " + g.value.positions[1];
      break;
    default:
      break;
  }
  var at = g.value.at;

  str += (at ? at + ", " : "")
         + ((shape || extent || at)
            ? (shape ? shape + " " : "")
              + (extent ? extent + " " : "")
              + (lengths ? lengths + " " : "")
              + ", "
            : "");
  for (var i = 0; i < g.value.stops.length; i++) {
    var s = g.value.stops[i];
    str += s.color
           + (s.position ? " " + s.position : "")
           + ((i != g.value.stops.length -1) ? ", " : "");
  }
  str += ")";

  switch (aEngine) {
    case "webkit":     str = "-webkit-"  + str; break;
    case "gecko1.9.2": str = "-moz-"  + str; break;
    case "presto":     str = "-o-"  + str; break;
    case "trident":    str = "-ms-"  + str; break;
    default:           break;
  }
  return str;
}

var CSS_ESCAPE  = '\\';

var IS_HEX_DIGIT  = 1;
var START_IDENT   = 2;
var IS_IDENT      = 4;
var IS_WHITESPACE = 8;

var W   = IS_WHITESPACE;
var I   = IS_IDENT;
var S   =          START_IDENT;
var SI  = IS_IDENT|START_IDENT;
var XI  = IS_IDENT            |IS_HEX_DIGIT;
var XSI = IS_IDENT|START_IDENT|IS_HEX_DIGIT;

function CSSScanner(aString)
{
  this.init(aString);
}

CSSScanner.prototype = {

  kLexTable: [
  //                                     TAB LF      FF  CR
     0,  0,  0,  0,  0,  0,  0,  0,  0,  W,  W,  0,  W,  W,  0,  0,
  //
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  // SPC !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
     W,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  I,  0,  0,
  // 0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
     XI, XI, XI, XI, XI, XI, XI, XI, XI, XI, 0,  0,  0,  0,  0,  0,
  // @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
     0,  XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI,
  // P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
     SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0,  S,  0,  0,  SI,
  // `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
     0,  XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI,
  // p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~
     SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0,  0,  0,  0,  0,
  //
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  //
     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  //     ¡   ¢   £   ¤   ¥   ¦   §   ¨   ©   ª   «   ¬   ­   ®   ¯
     0,  SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
  // °   ±   ²   ³   ´   µ   ¶   ·   ¸   ¹   º   »   ¼   ½   ¾   ¿
     SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
  // À   Á   Â   Ã   Ä   Å   Æ   Ç   È   É   Ê   Ë   Ì   Í   Î   Ï
     SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
  // Ð   Ñ   Ò   Ó   Ô   Õ   Ö   ×   Ø   Ù   Ú   Û   Ü   Ý   Þ   ß
     SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
  // à   á   â   ã   ä   å   æ   ç   è   é   ê   ë   ì   í   î   ï
     SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI,
  // ð   ñ   ò   ó   ô   õ   ö   ÷   ø   ù   ú   û   ü   ý   þ   ÿ
     SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI
  ],

  kHexValues: {
    "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9,
    "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15
  },

  mString : "",
  mPos : 0,
  mPreservedPos : [],

  init: function(aString) {
    this.mString = aString;
    this.mPos = 0;
    this.mPreservedPos = [];
  },

  getCurrentPos: function() {
    return this.mPos;
  },

  getAlreadyScanned: function()
  {
    return this.mString.substr(0, this.mPos);
  },

  preserveState: function() {
    this.mPreservedPos.push(this.mPos);
  },

  restoreState: function() {
    if (this.mPreservedPos.length) {
      this.mPos = this.mPreservedPos.pop();
    }
  },

  forgetState: function() {
    if (this.mPreservedPos.length) {
      this.mPreservedPos.pop();
    }
  },

  read: function() {
    if (this.mPos < this.mString.length)
      return this.mString.charAt(this.mPos++);
    return -1;
  },

  peek: function() {
    if (this.mPos < this.mString.length)
      return this.mString.charAt(this.mPos);
    return -1;
  },

  isHexDigit: function(c) {
    var code = c.charCodeAt(0);
    return (code < 256 && (this.kLexTable[code] & IS_HEX_DIGIT) != 0);
  },

  isIdentStart: function(c) {
    var code = c.charCodeAt(0);
    return (code >= 256 || (this.kLexTable[code] & START_IDENT) != 0);
  },

  startsWithIdent: function(aFirstChar, aSecondChar) {
    var code = aFirstChar.charCodeAt(0);
    return this.isIdentStart(aFirstChar) ||
           (aFirstChar == "-" && this.isIdentStart(aSecondChar));
  },

  isIdent: function(c) {
    var code = c.charCodeAt(0);
    return (code >= 256 || (this.kLexTable[code] & IS_IDENT) != 0);
  },

  pushback: function() {
    this.mPos--;
  },

  nextHexValue: function() {
    var c = this.read();
    if (c == -1 || !this.isHexDigit(c))
      return new jscsspToken(jscsspToken.NULL_TYPE, null);
    var s = c;
    c = this.read();
    while (c != -1 && this.isHexDigit(c)) {
      s += c;
      c = this.read();
    }
    if (c != -1)
      this.pushback();
    return new jscsspToken(jscsspToken.HEX_TYPE, s);
  },

  gatherEscape: function() {
    var c = this.peek();
    if (c == -1)
      return "";
    if (this.isHexDigit(c)) {
      var code = 0;
      for (var i = 0; i < 6; i++) {
        c = this.read();
        if (this.isHexDigit(c))
          code = code * 16 + this.kHexValues[c.toLowerCase()];
        else if (!this.isHexDigit(c) && !this.isWhiteSpace(c)) {
          this.pushback();
          break;
        }
        else
          break;
      }
      if (i == 6) {
        c = this.peek();
        if (this.isWhiteSpace(c))
          this.read();
      }
      return String.fromCharCode(code);
    }
    c = this.read();
    if (c != "\n")
      return c;
    return "";
  },

  gatherIdent: function(c) {
    var s = "";
    if (c == CSS_ESCAPE)
      s += this.gatherEscape();
    else
      s += c;
    c = this.read();
    while (c != -1
           && (this.isIdent(c) || c == CSS_ESCAPE)) {
      if (c == CSS_ESCAPE)
        s += this.gatherEscape();
      else
        s += c;
      c = this.read();
    }
    if (c != -1)
      this.pushback();
    return s;
  },

  parseIdent: function(c) {
    var value = this.gatherIdent(c);
    var nextChar = this.peek();
    if (nextChar == "(") {
      value += this.read();
      return new jscsspToken(jscsspToken.FUNCTION_TYPE, value);
    }
    return new jscsspToken(jscsspToken.IDENT_TYPE, value);
  },

  isDigit: function(c) {
    return (c >= '0') && (c <= '9');
  },

  parseComment: function(c) {
    var s = c;
    while ((c = this.read()) != -1) {
      s += c;
      if (c == "*") {
        c = this.read();
        if (c == -1)
          break;
        if (c == "/") {
          s += c;
          break;
        }
        this.pushback();
      }
    }
    return new jscsspToken(jscsspToken.COMMENT_TYPE, s);
  },

  parseNumber: function(c) {
    var s = c;
    var foundDot = false;
    while ((c = this.read()) != -1) {
      if (c == ".") {
        if (foundDot)
          break;
        else {
          s += c;
          foundDot = true;
        }
      } else if (this.isDigit(c))
        s += c;
      else
        break;
    }

    if (c != -1 && this.startsWithIdent(c, this.peek())) { // DIMENSION
      var unit = this.gatherIdent(c);
      s += unit;
      return new jscsspToken(jscsspToken.DIMENSION_TYPE, s, unit);
    }
    else if (c == "%") {
      s += "%";
      return new jscsspToken(jscsspToken.PERCENTAGE_TYPE, s);
    }
    else if (c != -1)
      this.pushback();
    return new jscsspToken(jscsspToken.NUMBER_TYPE, s);
  },

  parseString: function(aStop) {
    var s = aStop;
    var previousChar = aStop;
    var c;
    while ((c = this.read()) != -1) {
      if (c == aStop && previousChar != CSS_ESCAPE) {
        s += c;
        break;
      }
      else if (c == CSS_ESCAPE) {
        c = this.peek();
        if (c == -1)
          break;
        else if (c == "\n" || c == "\r" || c == "\f") {
          d = c;
          c = this.read();
          // special for Opera that preserves \r\n...
          if (d == "\r") {
            c = this.peek();
            if (c == "\n")
              c = this.read();
          }
        }
        else {
          s += this.gatherEscape();
          c = this.peek();
        }
      }
      else if (c == "\n" || c == "\r" || c == "\f") {
        break;
      }
      else
        s += c;

      previousChar = c;
    }
    return new jscsspToken(jscsspToken.STRING_TYPE, s);
  },

  isWhiteSpace: function(c) {
    var code = c.charCodeAt(0);
    return code < 256 && (this.kLexTable[code] & IS_WHITESPACE) != 0;
  },

  eatWhiteSpace: function(c) {
    var s = c;
    while ((c = this.read()) != -1) {
      if (!this.isWhiteSpace(c))
        break;
      s += c;
    }
    if (c != -1)
      this.pushback();
    return s;
  },

  parseAtKeyword: function(c) {
    return new jscsspToken(jscsspToken.ATRULE_TYPE, this.gatherIdent(c));
  },

  nextToken: function() {
    var c = this.read();
    if (c == -1)
      return new jscsspToken(jscsspToken.NULL_TYPE, null);

    if (this.startsWithIdent(c, this.peek()))
      return this.parseIdent(c);

    if (c == '@') {
      var nextChar = this.read();
      if (nextChar != -1) {
        var followingChar = this.peek();
        this.pushback();
        if (this.startsWithIdent(nextChar, followingChar))
          return this.parseAtKeyword(c);
      }
    }

    if (c == "." || c == "+" || c == "-") {
      var nextChar = this.peek();
      if (this.isDigit(nextChar))
        return this.parseNumber(c);
      else if (nextChar == "." && c != ".") {
        firstChar = this.read();
        var secondChar = this.peek();
        this.pushback();
        if (this.isDigit(secondChar))
          return this.parseNumber(c);
      }
    }
    if (this.isDigit(c)) {
      return this.parseNumber(c);
    }

    if (c == "'" || c == '"')
      return this.parseString(c);

    if (this.isWhiteSpace(c)) {
      var s = this.eatWhiteSpace(c);
      
      return new jscsspToken(jscsspToken.WHITESPACE_TYPE, s);
    }

    if (c == "|" || c == "~" || c == "^" || c == "$" || c == "*") {
      var nextChar = this.read();
      if (nextChar == "=") {
        switch (c) {
          case "~" :
            return new jscsspToken(jscsspToken.INCLUDES_TYPE, "~=");
          case "|" :
            return new jscsspToken(jscsspToken.DASHMATCH_TYPE, "|=");
          case "^" :
            return new jscsspToken(jscsspToken.BEGINSMATCH_TYPE, "^=");
          case "$" :
            return new jscsspToken(jscsspToken.ENDSMATCH_TYPE, "$=");
          case "*" :
            return new jscsspToken(jscsspToken.CONTAINSMATCH_TYPE, "*=");
          default :
            break;
        }
      } else if (nextChar != -1)
        this.pushback();
    }

    if (c == "/" && this.peek() == "*")
      return this.parseComment(c);

    return new jscsspToken(jscsspToken.SYMBOL_TYPE, c);
  }
};

CSSParser.prototype.parseBackgroundImages = function()
{
  var backgrounds = [];
  var token = this.getToken(true, true);
  while (token.isNotNull()) {
    if (token.isFunction("url(")) {
      token = this.getToken(true, true);
      var urlContent = this.parseURL(token);
      backgrounds.push( { type: "image", value: "url(" + urlContent });
      token = this.getToken(true, true);
    }
    else if (token.isFunction("linear-gradient(")
             || token.isFunction("radial-gradient(")
             || token.isFunction("repeating-linear-gradient(")
             || token.isFunction("repeating-radial-gradient(")) {
      this.ungetToken();
      var gradient = this.parseGradient();
      if (gradient) {
        backgrounds.push({
                         type: gradient.isRadial ? "radial-gradient" : "linear-gradient",
                         value: gradient
                         });
        token = this.getToken(true, true);
      }
      else
        return null;
    }
    else if (token.isIdent("none")
             || token.isIdent("inherit")
             || token.isIdent("initial")) {
      backgrounds.push( { type: token.value });
      token = this.getToken(true, true);
    }
    else
      return null;
    
    if (token.isSymbol(",")) {
      token = this.getToken(true, true);
      if (!token.isNotNull())
        return null;
    }
  }
  return backgrounds;
};

CSSParser.prototype.parseBackgroundShorthand = function(token, aDecl, aAcceptPriority)
{
  var kHPos = {
    "left" : true,
    "right" : true
  };
  var kVPos = {
    "top" : true,
    "bottom" : true
  };
  var kPos = {
    "left" : true,
    "right" : true,
    "top" : true,
    "bottom" : true,
    "center" : true
  };
  
  var bgColor = null;
  var bgRepeat = null;
  var bgAttachment = null;
  var bgImage = null;
  var bgPosition = null;
  
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!bgColor
             && !bgRepeat
             && !bgAttachment
             && !bgImage
             && !bgPosition
             && token.isIdent(this.kINHERIT)) {
      bgColor = this.kINHERIT;
      bgRepeat = this.kINHERIT;
      bgAttachment = this.kINHERIT;
      bgImage = this.kINHERIT;
      bgPosition = this.kINHERIT;
    }
    
    else {
      if (!bgAttachment
          && (token.isIdent("scroll") || token.isIdent("fixed"))) {
        bgAttachment = token.value;
      }
      
      else if (!bgPosition
               && ((token.isIdent() && token.value in kPos)
                   || token.isDimension()
                   || token.isNumber("0")
                   || token.isPercentage())) {
                 bgPosition = token.value;
                 token = this.getToken(true, true);
                 if (token.isDimension()
                     || token.isNumber("0")
                     || token.isPercentage()) {
                   bgPosition += " " + token.value;
                 } else if (token.isIdent() && token.value in kPos) {
                   if ((bgPosition in kHPos && token.value in kHPos)
                       || (bgPosition in kVPos && token.value in kVPos))
                     return "";
                   bgPosition += " " + token.value;
                 } else {
                   this.ungetToken();
                   bgPosition += " center";
                 }
               }
      
      else if (!bgRepeat
               && (token.isIdent("repeat")
                   || token.isIdent("repeat-x")
                   || token.isIdent("repeat-y")
                   || token.isIdent("no-repeat"))) {
                 bgRepeat = token.value;
               }
      
      else if (!bgImage
               && (token.isFunction("url(") || token.isIdent("none"))) {
        bgImage = token.value;
        if (token.isFunction("url(")) {
          token = this.getToken(true, true);
          var url = this.parseURL(token); // TODO
          if (url)
            bgImage += url;
          else
            return "";
        }
      }
      
      else if (!bgImage
               && (token.isFunction("linear-gradient(")
                   || token.isFunction("radial-gradient(")
                   || token.isFunction("repeating-linear-gradient(") || token.isFunction("repeating-radial-gradient("))) {
                 this.ungetToken();
                 var gradient = this.parseGradient();
                 if (gradient)
                   bgImage = this.serializeGradient(gradient);
                 else
                   return "";
               }
      
      else {
        var color = this.parseColor(token);
        if (!bgColor && color)
          bgColor = color;
        else
          return "";
      }
      
    }
    
    token = this.getToken(true, true);
  }
  
  // create the declarations
  this.forgetState();
  bgColor = bgColor ? bgColor : "transparent";
  bgImage = bgImage ? bgImage : "none";
  bgRepeat = bgRepeat ? bgRepeat : "repeat";
  bgAttachment = bgAttachment ? bgAttachment : "scroll";
  bgPosition = bgPosition ? bgPosition : "top left";
  
  aDecl.push(this._createJscsspDeclarationFromValue("background-color", bgColor));
  aDecl.push(this._createJscsspDeclarationFromValue("background-image", bgImage));
  aDecl.push(this._createJscsspDeclarationFromValue("background-repeat", bgRepeat));
  aDecl.push(this._createJscsspDeclarationFromValue("background-attachment", bgAttachment));
  aDecl.push(this._createJscsspDeclarationFromValue("background-position", bgPosition));
  
  return bgColor + " " + bgImage + " " + bgRepeat + " " + bgAttachment + " " + bgPosition;
};
CSSParser.prototype.parseBorderColorShorthand = function(token, aDecl, aAcceptPriority)
{
  var top = null;
  var bottom = null;
  var left = null;
  var right = null;
  
  var values = [];
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!values.length && token.isIdent(this.kINHERIT)) {
      values.push(token.value);
      token = this.getToken(true, true);
      break;
    }
    
    else {
      var color = this.parseColor(token);
      if (color)
        values.push(color);
      else
        return "";
    }
    
    token = this.getToken(true, true);
  }
  
  var count = values.length;
  switch (count) {
    case 1:
      top = values[0];
      bottom = top;
      left = top;
      right = top;
      break;
    case 2:
      top = values[0];
      bottom = top;
      left = values[1];
      right = left;
      break;
    case 3:
      top = values[0];
      left = values[1];
      right = left;
      bottom = values[2];
      break;
    case 4:
      top = values[0];
      right = values[1];
      bottom = values[2];
      left = values[3];
      break;
    default:
      return "";
  }
  this.forgetState();
  aDecl.push(this._createJscsspDeclarationFromValue("border-top-color", top));
  aDecl.push(this._createJscsspDeclarationFromValue("border-right-color", right));
  aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-color", bottom));
  aDecl.push(this._createJscsspDeclarationFromValue("border-left-color", left));
  return top + " " + right + " " + bottom + " " + left;
};

CSSParser.prototype.parseBorderEdgeOrOutlineShorthand = function(token, aDecl, aAcceptPriority, aProperty)
{
  var bWidth = null;
  var bStyle = null;
  var bColor = null;
  
  while (true) {
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!bWidth
             && !bStyle
             && !bColor
             && token.isIdent(this.kINHERIT)) {
      bWidth = this.kINHERIT;
      bStyle = this.kINHERIT;
      bColor = this.kINHERIT;
    }
    
    else if (!bWidth &&
             (token.isDimension()
              || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)
              || token.isNumber("0"))) {
               bWidth = token.value;
             }
    
    else if (!bStyle &&
             (token.isIdent() && token.value in this.kBORDER_STYLE_NAMES)) {
      bStyle = token.value;
    }
    
    else {
      var color = (aProperty == "outline" && token.isIdent("invert"))
      ? "invert" : this.parseColor(token);
      if (!bColor && color)
        bColor = color;
      else
        return "";
    }
    token = this.getToken(true, true);
  }
  
  // create the declarations
  this.forgetState();
  bWidth = bWidth ? bWidth : "medium";
  bStyle = bStyle ? bStyle : "none";
  bColor = bColor ? bColor : "-moz-initial";
  
  function addPropertyToDecl(aSelf, aDecl, property, w, s, c) {
    aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-width", w));
    aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-style", s));
    aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-color", c));
  }
  
  if (aProperty == "border") {
    addPropertyToDecl(this, aDecl, "border-top", bWidth, bStyle, bColor);
    addPropertyToDecl(this, aDecl, "border-right", bWidth, bStyle, bColor);
    addPropertyToDecl(this, aDecl, "border-bottom", bWidth, bStyle, bColor);
    addPropertyToDecl(this, aDecl, "border-left", bWidth, bStyle, bColor);
  }
  else
    addPropertyToDecl(this, aDecl, aProperty, bWidth, bStyle, bColor);
  return bWidth + " " + bStyle + " " + bColor;
};

CSSParser.prototype.parseBorderImage = function()
{
  var borderImage = {url: "", offsets: [], widths: [], sizes: []};
  var token = this.getToken(true, true);
  if (token.isFunction("url(")) {
    token = this.getToken(true, true);
    var urlContent = this.parseURL(token);
    if (urlContent) {
      borderImage.url = urlContent.substr(0, urlContent.length - 1).trim();
      if ((borderImage.url[0] == '"' && borderImage.url[borderImage.url.length - 1] == '"')
          || (borderImage.url[0] == "'" && borderImage.url[borderImage.url.length - 1] == "'"))
        borderImage.url = borderImage.url.substr(1, borderImage.url.length - 2);
    }
    else
      return null;
  }
  else
    return null; 
  
  token = this.getToken(true, true);
  if (token.isNumber()
      || token.isPercentage())
    borderImage.offsets.push(token.value);
  else
    return null;
  var i;
  for (i= 0; i < 3; i++) {
    token = this.getToken(true, true);
    if (token.isNumber()
        || token.isPercentage())
      borderImage.offsets.push(token.value);
    else
      break;
  }
  if (i == 3)
    token = this.getToken(true, true);
  
  if (token.isSymbol("/")) {
    token = this.getToken(true, true);
    if (token.isDimension()
        || token.isNumber("0")
        || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES))
      borderImage.widths.push(token.value);
    else
      return null;
    
    for (var i = 0; i < 3; i++) {
      token = this.getToken(true, true);
      if (token.isDimension()
          || token.isNumber("0")
          || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES))
        borderImage.widths.push(token.value);
      else
        break;
    }
    if (i == 3)
      token = this.getToken(true, true);
  }
  
  for (var i = 0; i < 2; i++) {
    if (token.isIdent("stretch")
        || token.isIdent("repeat")
        || token.isIdent("round"))
      borderImage.sizes.push(token.value);
    else if (!token.isNotNull())
      return borderImage;
    else
      return null;
    token = this.getToken(true, true);
  }
  if (!token.isNotNull())
    return borderImage;
  
  return null;
};

CSSParser.prototype.parseBorderStyleShorthand = function(token, aDecl, aAcceptPriority)
{
  var top = null;
  var bottom = null;
  var left = null;
  var right = null;
  
  var values = [];
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!values.length && token.isIdent(this.kINHERIT)) {
      values.push(token.value);
    }
    
    else if (token.isIdent() && token.value in this.kBORDER_STYLE_NAMES) {
      values.push(token.value);
    }
    else
      return "";
    
    token = this.getToken(true, true);
  }
  
  var count = values.length;
  switch (count) {
    case 1:
      top = values[0];
      bottom = top;
      left = top;
      right = top;
      break;
    case 2:
      top = values[0];
      bottom = top;
      left = values[1];
      right = left;
      break;
    case 3:
      top = values[0];
      left = values[1];
      right = left;
      bottom = values[2];
      break;
    case 4:
      top = values[0];
      right = values[1];
      bottom = values[2];
      left = values[3];
      break;
    default:
      return "";
  }
  this.forgetState();
  aDecl.push(this._createJscsspDeclarationFromValue("border-top-style", top));
  aDecl.push(this._createJscsspDeclarationFromValue("border-right-style", right));
  aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-style", bottom));
  aDecl.push(this._createJscsspDeclarationFromValue("border-left-style", left));
  return top + " " + right + " " + bottom + " " + left;
};

CSSParser.prototype.parseBorderWidthShorthand = function(token, aDecl, aAcceptPriority)
{
  var top = null;
  var bottom = null;
  var left = null;
  var right = null;
  
  var values = [];
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!values.length && token.isIdent(this.kINHERIT)) {
      values.push(token.value);
    }
    
    else if (token.isDimension()
             || token.isNumber("0")
             || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)) {
      values.push(token.value);
    }
    else
      return "";
    
    token = this.getToken(true, true);
  }
  
  var count = values.length;
  switch (count) {
    case 1:
      top = values[0];
      bottom = top;
      left = top;
      right = top;
      break;
    case 2:
      top = values[0];
      bottom = top;
      left = values[1];
      right = left;
      break;
    case 3:
      top = values[0];
      left = values[1];
      right = left;
      bottom = values[2];
      break;
    case 4:
      top = values[0];
      right = values[1];
      bottom = values[2];
      left = values[3];
      break;
    default:
      return "";
  }
  this.forgetState();
  aDecl.push(this._createJscsspDeclarationFromValue("border-top-width", top));
  aDecl.push(this._createJscsspDeclarationFromValue("border-right-width", right));
  aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-width", bottom));
  aDecl.push(this._createJscsspDeclarationFromValue("border-left-width", left));
  return top + " " + right + " " + bottom + " " + left;
};

CSSParser.prototype.parseBoxShadows = function()
{
  var shadows = [];
  var token = this.getToken(true, true);
  var color = "", blurRadius = "0px", offsetX = "0px", offsetY = "0px", spreadRadius = "0px";
  var inset = false;
  while (token.isNotNull()) {
    if (token.isIdent("none")) {
      shadows.push( { none: true } );
      token = this.getToken(true, true);
    }
    else {
      if (token.isIdent('inset')) {
        inset = true;
        token = this.getToken(true, true);
      }
      
      if (token.isPercentage() ||
          token.isDimensionOfUnit("cm") ||
          token.isDimensionOfUnit("mm") ||
          token.isDimensionOfUnit("in") ||
          token.isDimensionOfUnit("pc") ||
          token.isDimensionOfUnit("px") ||
          token.isDimensionOfUnit("em") ||
          token.isDimensionOfUnit("ex") ||
          token.isDimensionOfUnit("pt")) {
        var offsetX = token.value;
        token = this.getToken(true, true);
      }
      else
        return [];
      
      if (!inset && token.isIdent('inset')) {
        inset = true;
        token = this.getToken(true, true);
      }
      
      if (token.isPercentage() ||
          token.isDimensionOfUnit("cm") ||
          token.isDimensionOfUnit("mm") ||
          token.isDimensionOfUnit("in") ||
          token.isDimensionOfUnit("pc") ||
          token.isDimensionOfUnit("px") ||
          token.isDimensionOfUnit("em") ||
          token.isDimensionOfUnit("ex") ||
          token.isDimensionOfUnit("pt")) {
        var offsetY = token.value;
        token = this.getToken(true, true);
      }
      else
        return [];
      
      if (!inset && token.isIdent('inset')) {
        inset = true;
        token = this.getToken(true, true);
      }
      
      if (token.isPercentage() ||
          token.isDimensionOfUnit("cm") ||
          token.isDimensionOfUnit("mm") ||
          token.isDimensionOfUnit("in") ||
          token.isDimensionOfUnit("pc") ||
          token.isDimensionOfUnit("px") ||
          token.isDimensionOfUnit("em") ||
          token.isDimensionOfUnit("ex") ||
          token.isDimensionOfUnit("pt")) {
        var blurRadius = token.value;
        token = this.getToken(true, true);
      }
      
      if (!inset && token.isIdent('inset')) {
        inset = true;
        token = this.getToken(true, true);
      }
      
      if (token.isPercentage() ||
          token.isDimensionOfUnit("cm") ||
          token.isDimensionOfUnit("mm") ||
          token.isDimensionOfUnit("in") ||
          token.isDimensionOfUnit("pc") ||
          token.isDimensionOfUnit("px") ||
          token.isDimensionOfUnit("em") ||
          token.isDimensionOfUnit("ex") ||
          token.isDimensionOfUnit("pt")) {
        var spreadRadius = token.value;
        token = this.getToken(true, true);
      }
      
      if (!inset && token.isIdent('inset')) {
        inset = true;
        token = this.getToken(true, true);
      }
      
      if (token.isFunction("rgb(") ||
          token.isFunction("rgba(") ||
          token.isFunction("hsl(") ||
          token.isFunction("hsla(") ||
          token.isSymbol("#") ||
          token.isIdent()) {
        var color = this.parseColor(token);
        token = this.getToken(true, true);
      }
      
      if (!inset && token.isIdent('inset')) {
        inset = true;
        token = this.getToken(true, true);
      }
      
      shadows.push( { none: false,
                   color: color,
                   offsetX: offsetX, offsetY: offsetY,
                   blurRadius: blurRadius,
                   spreadRadius: spreadRadius,
                   inset: inset
                   } );
      
      if (token.isSymbol(",")) {
        inset = false;
        color = "";
        blurRadius = "0px";
        spreadRadius = "0px"
        offsetX = "0px";
        offsetY = "0px"; 
        token = this.getToken(true, true);
      }
      else if (!token.isNotNull())
        return shadows;
      else
        return [];
    }
  }
  return shadows;
};

CSSParser.prototype.parseCharsetRule = function(aSheet) {
  var token = this.getToken(false, false);
  if (token.isAtRule("@charset") && token.value == "@charset") { // lowercase check
    var s = token.value;
    token = this.getToken(false, false);
    s += token.value;
    if (token.isWhiteSpace(" ")) {
      token = this.getToken(false, false);
      s += token.value;
      if (token.isString()) {
        var encoding = token.value;
        token = this.getToken(false, false);
        s += token.value;
        if (token.isSymbol(";")) {
          var rule = new jscsspCharsetRule();
          rule.encoding = encoding;
          rule.parsedCssText = s;
          rule.parentStyleSheet = aSheet;
          aSheet.cssRules.push(rule);
          return true;
        }
        else
          this.reportError(kCHARSET_RULE_MISSING_SEMICOLON);
      }
      else
        this.reportError(kCHARSET_RULE_CHARSET_IS_STRING);
    }
    else
      this.reportError(kCHARSET_RULE_MISSING_WS);
  }
  
  this.addUnknownAtRule(aSheet, s);
  return false;
};

CSSParser.prototype.parseColor = function(token)
{
  var color = "";
  if (token.isFunction("rgb(")
      || token.isFunction("rgba(")) {
    color = token.value;
    var isRgba = token.isFunction("rgba(")
    token = this.getToken(true, true);
    if (!token.isNumber() && !token.isPercentage())
      return "";
    color += token.value;
    token = this.getToken(true, true);
    if (!token.isSymbol(","))
      return "";
    color += ", ";
    
    token = this.getToken(true, true);
    if (!token.isNumber() && !token.isPercentage())
      return "";
    color += token.value;
    token = this.getToken(true, true);
    if (!token.isSymbol(","))
      return "";
    color += ", ";
    
    token = this.getToken(true, true);
    if (!token.isNumber() && !token.isPercentage())
      return "";
    color += token.value;
    
    if (isRgba) {
      token = this.getToken(true, true);
      if (!token.isSymbol(","))
        return "";
      color += ", ";
      
      token = this.getToken(true, true);
      if (!token.isNumber())
        return "";
      color += token.value;
    }
    
    token = this.getToken(true, true);
    if (!token.isSymbol(")"))
      return "";
    color += token.value;
  }
  
  else if (token.isFunction("hsl(")
           || token.isFunction("hsla(")) {
    color = token.value;
    var isHsla = token.isFunction("hsla(")
    token = this.getToken(true, true);
    if (!token.isNumber())
      return "";
    color += token.value;
    token = this.getToken(true, true);
    if (!token.isSymbol(","))
      return "";
    color += ", ";
    
    token = this.getToken(true, true);
    if (!token.isPercentage())
      return "";
    color += token.value;
    token = this.getToken(true, true);
    if (!token.isSymbol(","))
      return "";
    color += ", ";
    
    token = this.getToken(true, true);
    if (!token.isPercentage())
      return "";
    color += token.value;
    
    if (isHsla) {
      token = this.getToken(true, true);
      if (!token.isSymbol(","))
        return "";
      color += ", ";
      
      token = this.getToken(true, true);
      if (!token.isNumber())
        return "";
      color += token.value;
    }
    
    token = this.getToken(true, true);
    if (!token.isSymbol(")"))
      return "";
    color += token.value;
  }
  
  else if (token.isIdent()
           && (token.value in this.kCOLOR_NAMES))
    color = token.value;
  
  else if (token.isSymbol("#")) {
    token = this.getHexValue();
    if (!token.isHex())
      return "";
    var length = token.value.length;
    if (length != 3 && length != 6)
      return "";
    if (token.value.match( /[a-fA-F0-9]/g ).length != length)
      return "";
    color = "#" + token.value;
  }
  return color;
};

CSSParser.prototype.parseCueShorthand = function(token, declarations, aAcceptPriority)
{
  var before = "";
  var after = "";
  
  var values = [];
  var values = [];
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!values.length && token.isIdent(this.kINHERIT)) {
      values.push(token.value);
    }
    
    else if (token.isIdent("none"))
      values.push(token.value);
    
    else if (token.isFunction("url(")) {
      token = this.getToken(true, true);
      var urlContent = this.parseURL(token);
      if (urlContent)
        values.push("url(" + urlContent);
      else
        return "";
    }
    else
      return "";
    
    token = this.getToken(true, true);
  }
  
  var count = values.length;
  switch (count) {
    case 1:
      before = values[0];
      after = before;
      break;
    case 2:
      before = values[0];
      after = values[1];
      break;
    default:
      return "";
  }
  this.forgetState();
  aDecl.push(this._createJscsspDeclarationFromValue("cue-before", before));
  aDecl.push(this._createJscsspDeclarationFromValue("cue-after", after));
  return before + " " + after;
};

CSSParser.prototype.parseDeclaration = function(aToken, aDecl, aAcceptPriority, aExpandShorthands, aSheet) {
  this.preserveState();
  var blocks = [];
  if (aToken.isIdent()) {
    var descriptor = aToken.value.toLowerCase();
    var token = this.getToken(true, true);
    if (token.isSymbol(":")) {
      var token = this.getToken(true, true);
      
      var value = "";
      var declarations = [];
      if (aExpandShorthands)
        switch (descriptor) {
          case "background":
            value = this.parseBackgroundShorthand(token, declarations, aAcceptPriority);
            break;
          case "margin":
          case "padding":
            value = this.parseMarginOrPaddingShorthand(token, declarations, aAcceptPriority, descriptor);
            break;
          case "border-color":
            value = this.parseBorderColorShorthand(token, declarations, aAcceptPriority);
            break;
          case "border-style":
            value = this.parseBorderStyleShorthand(token, declarations, aAcceptPriority);
            break;
          case "border-width":
            value = this.parseBorderWidthShorthand(token, declarations, aAcceptPriority);
            break;
          case "border-top":
          case "border-right":
          case "border-bottom":
          case "border-left":
          case "border":
          case "outline":
            value = this.parseBorderEdgeOrOutlineShorthand(token, declarations, aAcceptPriority, descriptor);
            break;
          case "cue":
            value = this.parseCueShorthand(token, declarations, aAcceptPriority);
            break;
          case "pause":
            value = this.parsePauseShorthand(token, declarations, aAcceptPriority);
            break;
          case "font":
            value = this.parseFontShorthand(token, declarations, aAcceptPriority);
            break;
          case "list-style":
            value = this.parseListStyleShorthand(token, declarations, aAcceptPriority);
            break;
          default:
            value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet);
            break;
        }
      else
        value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet);
      token = this.currentToken();
      if (value) // no error above
      {
        var priority = false;
        if (token.isSymbol("!")) {
          token = this.getToken(true, true);
          if (token.isIdent("important")) {
            priority = true;
            token = this.getToken(true, true);
            if (token.isSymbol(";") || token.isSymbol("}")) {
              if (token.isSymbol("}"))
                this.ungetToken();
            }
            else return "";
          }
          else return "";
        }
        else if  (token.isNotNull() && !token.isSymbol(";") && !token.isSymbol("}"))
          return "";
        for (var i = 0; i < declarations.length; i++) {
          declarations[i].priority = priority;
          aDecl.push(declarations[i]);
        }
        return descriptor + ": " + value + ";";
      }
    }
  }
  else if (aToken.isComment()) {
    if (this.mPreserveComments) {
      this.forgetState();
      var comment = new jscsspComment();
      comment.parsedCssText = aToken.value;
      aDecl.push(comment);
    }
    return aToken.value;
  }
  
  // we have an error here, let's skip it
  this.restoreState();
  var s = aToken.value;
  blocks = [];
  var token = this.getToken(false, false);
  while (token.isNotNull()) {
    s += token.value;
    if ((token.isSymbol(";") || token.isSymbol("}")) && !blocks.length) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    } else if (token.isSymbol("{")
               || token.isSymbol("(")
               || token.isSymbol("[")
               || token.isFunction()) {
      blocks.push(token.isFunction() ? "(" : token.value);
    } else if (token.isSymbol("}")
               || token.isSymbol(")")
               || token.isSymbol("]")) {
      if (blocks.length) {
        var ontop = blocks[blocks.length - 1];
        if ((token.isSymbol("}") && ontop == "{")
            || (token.isSymbol(")") && ontop == "(")
            || (token.isSymbol("]") && ontop == "[")) {
          blocks.pop();
        }
      }
    }
    token = this.getToken(false, false);
  }
  return "";
};

CSSParser.prototype.reportError = function(aMsg) {
  this.mError = aMsg;
};

CSSParser.prototype.consumeError = function() {
  var e = this.mError;
  this.mError = null;
  return e;
};

function CSSParser(aString)
{
  this.mToken = null;
  this.mLookAhead = null;
  this.mScanner = new CSSScanner(aString);
  
  this.mPreserveWS = true;
  this.mPreserveComments = true;
  
  this.mPreservedTokens = [];
  
  this.mError = null;
}

CSSParser.prototype._init = function() {
  this.mToken = null;
  this.mLookAhead = null;
};

CSSParser.prototype.kINHERIT = "inherit",

CSSParser.prototype.kBORDER_WIDTH_NAMES = {
  "thin": true,
  "medium": true,
  "thick": true
};

CSSParser.prototype.kBORDER_STYLE_NAMES = {
  "none": true,
  "hidden": true,
  "dotted": true,
  "dashed": true,
  "solid": true,
  "double": true,
  "groove": true,
  "ridge": true,
  "inset": true,
  "outset": true
};

CSSParser.prototype.kCOLOR_NAMES = {
  "transparent": true,
  
  "black": true,
  "silver": true,
  "gray": true,
  "white": true,
  "maroon": true,
  "red": true,
  "purple": true,
  "fuchsia": true,
  "green": true,
  "lime": true,
  "olive": true,
  "yellow": true,
  "navy": true,
  "blue": true,
  "teal": true,
  "aqua": true,
  
  "aliceblue": true,
  "antiquewhite": true,
  "aquamarine": true,
  "azure": true,
  "beige": true,
  "bisque": true,
  "blanchedalmond": true,
  "blueviolet": true,
  "brown": true,
  "burlywood": true,
  "cadetblue": true,
  "chartreuse": true,
  "chocolate": true,
  "coral": true,
  "cornflowerblue": true,
  "cornsilk": true,
  "crimson": true,
  "cyan": true,
  "darkblue": true,
  "darkcyan": true,
  "darkgoldenrod": true,
  "darkgray": true,
  "darkgreen": true,
  "darkgrey": true,
  "darkkhaki": true,
  "darkmagenta": true,
  "darkolivegreen": true,
  "darkorange": true,
  "darkorchid": true,
  "darkred": true,
  "darksalmon": true,
  "darkseagreen": true,
  "darkslateblue": true,
  "darkslategray": true,
  "darkslategrey": true,
  "darkturquoise": true,
  "darkviolet": true,
  "deeppink": true,
  "deepskyblue": true,
  "dimgray": true,
  "dimgrey": true,
  "dodgerblue": true,
  "firebrick": true,
  "floralwhite": true,
  "forestgreen": true,
  "gainsboro": true,
  "ghostwhite": true,
  "gold": true,
  "goldenrod": true,
  "greenyellow": true,
  "grey": true,
  "honeydew": true,
  "hotpink": true,
  "indianred": true,
  "indigo": true,
  "ivory": true,
  "khaki": true,
  "lavender": true,
  "lavenderblush": true,
  "lawngreen": true,
  "lemonchiffon": true,
  "lightblue": true,
  "lightcoral": true,
  "lightcyan": true,
  "lightgoldenrodyellow": true,
  "lightgray": true,
  "lightgreen": true,
  "lightgrey": true,
  "lightpink": true,
  "lightsalmon": true,
  "lightseagreen": true,
  "lightskyblue": true,
  "lightslategray": true,
  "lightslategrey": true,
  "lightsteelblue": true,
  "lightyellow": true,
  "limegreen": true,
  "linen": true,
  "magenta": true,
  "mediumaquamarine": true,
  "mediumblue": true,
  "mediumorchid": true,
  "mediumpurple": true,
  "mediumseagreen": true,
  "mediumslateblue": true,
  "mediumspringgreen": true,
  "mediumturquoise": true,
  "mediumvioletred": true,
  "midnightblue": true,
  "mintcream": true,
  "mistyrose": true,
  "moccasin": true,
  "navajowhite": true,
  "oldlace": true,
  "olivedrab": true,
  "orange": true,
  "orangered": true,
  "orchid": true,
  "palegoldenrod": true,
  "palegreen": true,
  "paleturquoise": true,
  "palevioletred": true,
  "papayawhip": true,
  "peachpuff": true,
  "peru": true,
  "pink": true,
  "plum": true,
  "powderblue": true,
  "rosybrown": true,
  "royalblue": true,
  "saddlebrown": true,
  "salmon": true,
  "sandybrown": true,
  "seagreen": true,
  "seashell": true,
  "sienna": true,
  "skyblue": true,
  "slateblue": true,
  "slategray": true,
  "slategrey": true,
  "snow": true,
  "springgreen": true,
  "steelblue": true,
  "tan": true,
  "thistle": true,
  "tomato": true,
  "turquoise": true,
  "violet": true,
  "wheat": true,
  "whitesmoke": true,
  "yellowgreen": true,
  
  "activeborder": true,
  "activecaption": true,
  "appworkspace": true,
  "background": true,
  "buttonface": true,
  "buttonhighlight": true,
  "buttonshadow": true,
  "buttontext": true,
  "captiontext": true,
  "graytext": true,
  "highlight": true,
  "highlighttext": true,
  "inactiveborder": true,
  "inactivecaption": true,
  "inactivecaptiontext": true,
  "infobackground": true,
  "infotext": true,
  "menu": true,
  "menutext": true,
  "scrollbar": true,
  "threeddarkshadow": true,
  "threedface": true,
  "threedhighlight": true,
  "threedlightshadow": true,
  "threedshadow": true,
  "window": true,
  "windowframe": true,
  "windowtext": true
};

CSSParser.prototype.kLIST_STYLE_TYPE_NAMES = {
  "decimal": true,
  "decimal-leading-zero": true,
  "lower-roman": true,
  "upper-roman": true,
  "georgian": true,
  "armenian": true,
  "lower-latin": true,
  "lower-alpha": true,
  "upper-latin": true,
  "upper-alpha": true,
  "lower-greek": true,
  
  "disc": true,
  "circle": true,
  "square": true,
  "none": true,
  
  /* CSS 3 */
  "box": true,
  "check": true,
  "diamond": true,
  "hyphen": true,
  
  "lower-armenian": true,
  "cjk-ideographic": true,
  "ethiopic-numeric": true,
  "hebrew": true,
  "japanese-formal": true,
  "japanese-informal": true,
  "simp-chinese-formal": true,
  "simp-chinese-informal": true,
  "syriac": true,
  "tamil": true,
  "trad-chinese-formal": true,
  "trad-chinese-informal": true,
  "upper-armenian": true,
  "arabic-indic": true,
  "binary": true,
  "bengali": true,
  "cambodian": true,
  "khmer": true,
  "devanagari": true,
  "gujarati": true,
  "gurmukhi": true,
  "kannada": true,
  "lower-hexadecimal": true,
  "lao": true,
  "malayalam": true,
  "mongolian": true,
  "myanmar": true,
  "octal": true,
  "oriya": true,
  "persian": true,
  "urdu": true,
  "telugu": true,
  "tibetan": true,
  "upper-hexadecimal": true,
  "afar": true,
  "ethiopic-halehame-aa-et": true,
  "ethiopic-halehame-am-et": true,
  "amharic-abegede": true,
  "ehiopic-abegede-am-et": true,
  "cjk-earthly-branch": true,
  "cjk-heavenly-stem": true,
  "ethiopic": true,
  "ethiopic-abegede": true,
  "ethiopic-abegede-gez": true,
  "hangul-consonant": true,
  "hangul": true,
  "hiragana-iroha": true,
  "hiragana": true,
  "katakana-iroha": true,
  "katakana": true,
  "lower-norwegian": true,
  "oromo": true,
  "ethiopic-halehame-om-et": true,
  "sidama": true,
  "ethiopic-halehame-sid-et": true,
  "somali": true,
  "ethiopic-halehame-so-et": true,
  "tigre": true,
  "ethiopic-halehame-tig": true,
  "tigrinya-er-abegede": true,
  "ethiopic-abegede-ti-er": true,
  "tigrinya-et": true,
  "ethiopic-halehame-ti-et": true,
  "upper-greek": true,
  "asterisks": true,
  "footnotes": true,
  "circled-decimal": true,
  "circled-lower-latin": true,
  "circled-upper-latin": true,
  "dotted-decimal": true,
  "double-circled-decimal": true,
  "filled-circled-decimal": true,
  "parenthesised-decimal": true,
  "parenthesised-lower-latin": true
};

CSSParser.prototype.parseFontFaceRule = function(aToken, aSheet) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var s = aToken.value;
  var valid = false;
  var descriptors = [];
  this.preserveState();
  var token = this.getToken(true, true);
  if (token.isNotNull()) {
    // expecting block start
    if (token.isSymbol("{")) {
      s += " " + token.value;
      var token = this.getToken(true, false);
      while (true) {
        if (token.isSymbol("}")) {
          s += "}";
          valid = true;
          break;
        } else {
          var d = this.parseDeclaration(token, descriptors, false, false, aSheet);
          s += ((d && descriptors.length) ? " " : "") + d;
        }
        token = this.getToken(true, false);
      }
    }
  }
  if (valid) {
    this.forgetState();
    var rule = new jscsspFontFaceRule();
    rule.currentLine = currentLine;
    rule.parsedCssText = s;
    rule.descriptors = descriptors;
    rule.parentStyleSheet = aSheet;
    aSheet.cssRules.push(rule)
    return true;
  }
  this.restoreState();
  return false;
};

CSSParser.prototype.parseFontShorthand = function(token, aDecl, aAcceptPriority)
{
  var kStyle = {"italic": true, "oblique": true };
  var kVariant = {"small-caps": true };
  var kWeight = { "bold": true, "bolder": true, "lighter": true,
    "100": true, "200": true, "300": true, "400": true,
    "500": true, "600": true, "700": true, "800": true,
    "900": true };
  var kSize = { "xx-small": true, "x-small": true, "small": true, "medium": true,
    "large": true, "x-large": true, "xx-large": true,
    "larger": true, "smaller": true };
  var kValues = { "caption": true, "icon": true, "menu": true, "message-box": true, "small-caption": true, "status-bar": true };
  var kFamily = { "serif": true, "sans-serif": true, "cursive": true, "fantasy": true, "monospace": true };
  
  var fStyle = null;
  var fVariant = null;
  var fWeight = null;
  var fSize = null;
  var fLineHeight = null;
  var fFamily = "";
  var fSystem = null;
  var fFamilyValues = [];
  
  var normalCount = 0;
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!fStyle && !fVariant && !fWeight
             && !fSize && !fLineHeight && !fFamily
             && !fSystem
             && token.isIdent(this.kINHERIT)) {
      fStyle = this.kINHERIT;
      fVariant = this.kINHERIT;
      fWeight = this.kINHERIT;
      fSize = this.kINHERIT;
      fLineHeight = this.kINHERIT;
      fFamily = this.kINHERIT;
      fSystem = this.kINHERIT;
    }
    
    else {
      if (!fSystem && (token.isIdent() && token.value in kValues)) {
        fSystem = token.value;
        break;
      }
      
      else {
        if (!fStyle
            && token.isIdent()
            && (token.value in kStyle)) {
          fStyle = token.value;
        }
        
        else if (!fVariant
                 && token.isIdent()
                 && (token.value in kVariant)) {
          fVariant = token.value;
        }
        
        else if (!fWeight
                 && (token.isIdent() || token.isNumber())
                 && (token.value in kWeight)) {
          fWeight = token.value;
        }
        
        else if (!fSize
                 && ((token.isIdent() && (token.value in kSize))
                     || token.isDimension()
                     || token.isPercentage())) {
                   fSize = token.value;
                   token = this.getToken(false, false);
                   if (token.isSymbol("/")) {
                     token = this.getToken(false, false);
                     if (!fLineHeight &&
                         (token.isDimension() || token.isNumber() || token.isPercentage())) {
                       fLineHeight = token.value;
                     }
                     else
                       return "";
                   }
                   else if (!token.isWhiteSpace())
                     continue;
                 }
        
        else if (token.isIdent("normal")) {
          normalCount++;
          if (normalCount > 3)
            return "";
        }
        
        else if (!fFamily && // *MUST* be last to be tested here
                 (token.isString()
                  || token.isIdent())) {
                   var lastWasComma = false;
                   while (true) {
                     if (!token.isNotNull())
                       break;
                     else if (token.isSymbol(";")
                              || (aAcceptPriority && token.isSymbol("!"))
                              || token.isSymbol("}")) {
                       this.ungetToken();
                       break;
                     }
                     else if (token.isIdent() && token.value in kFamily) {
                       var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null);
                       value.value = token.value;
                       fFamilyValues.push(value);
                       fFamily += token.value;
                       break;
                     }
                     else if (token.isString() || token.isIdent()) {
                       var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null);
                       value.value = token.value;
                       fFamilyValues.push(value);
                       fFamily += token.value;
                       lastWasComma = false;
                     }
                     else if (!lastWasComma && token.isSymbol(",")) {
                       fFamily += ", ";
                       lastWasComma = true;
                     }
                     else
                       return "";
                     token = this.getToken(true, true);
                   }
                 }
        
        else {
          return "";
        }
      }
      
    }
    
    token = this.getToken(true, true);
  }
  
  // create the declarations
  this.forgetState();
  if (fSystem) {
    aDecl.push(this._createJscsspDeclarationFromValue("font", fSystem));
    return fSystem;
  }
  fStyle = fStyle ? fStyle : "normal";
  fVariant = fVariant ? fVariant : "normal";
  fWeight = fWeight ? fWeight : "normal";
  fSize = fSize ? fSize : "medium";
  fLineHeight = fLineHeight ? fLineHeight : "normal";
  fFamily = fFamily ? fFamily : "-moz-initial";
  
  aDecl.push(this._createJscsspDeclarationFromValue("font-style", fStyle));
  aDecl.push(this._createJscsspDeclarationFromValue("font-variant", fVariant));
  aDecl.push(this._createJscsspDeclarationFromValue("font-weight", fWeight));
  aDecl.push(this._createJscsspDeclarationFromValue("font-size", fSize));
  aDecl.push(this._createJscsspDeclarationFromValue("line-height", fLineHeight));
  aDecl.push(this._createJscsspDeclarationFromValuesArray("font-family", fFamilyValues, fFamily));
  return fStyle + " " + fVariant + " " + fWeight + " " + fSize + "/" + fLineHeight + " " + fFamily;
};

CSSParser.prototype.parseFunctionArgument = function(token)
{
  var value = "";
  if (token.isString())
  {
    value += token.value;
    token = this.getToken(true, true);
  }
  else {
    var parenthesis = 1;
    while (true)
    {
      if (!token.isNotNull())
        return "";
      if (token.isFunction() || token.isSymbol("("))
        parenthesis++;
      if (token.isSymbol(")")) {
        parenthesis--;
        if (!parenthesis)
          break;
      }
      value += token.value;
      token = this.getToken(false, false);
    }
  }
  
  if (token.isSymbol(")"))
    return value + ")";
  return "";
};

CSSParser.prototype.parseColorStop = function(token)
{
  var color = this.parseColor(token);
  var position = "";
  if (!color)
    return null;
  token = this.getToken(true, true);
  if (token.isLength()) {
    position = token.value;
    token = this.getToken(true, true);
  }
  return { color: color, position: position }
};

CSSParser.prototype.parseGradient = function ()
{
  var kHPos = {"left": true, "right": true };
  var kVPos = {"top": true, "bottom": true };
  var kPos = {"left": true, "right": true, "top": true, "bottom": true, "center": true};
  
  var isRadial = false;
  var gradient = { isRepeating: false };
  var token = this.getToken(true, true);
  if (token.isNotNull()) {
    if (token.isFunction("linear-gradient(") ||
        token.isFunction("radial-gradient(") ||
        token.isFunction("repeating-linear-gradient(") ||
        token.isFunction("repeating-radial-gradient(")) {
      if (token.isFunction("radial-gradient(") ||
          token.isFunction("repeating-radial-gradient(")) {
        gradient.isRadial = true;
      }
      if (token.isFunction("repeating-linear-gradient(") ||
          token.isFunction("repeating-radial-gradient(")) {
        gradient.isRepeating = true;
      }
      
      
      token = this.getToken(true, true);
      var foundPosition = false;
      var haveAngle = false;
      
      /********** LINEAR **********/
      if (token.isAngle()) {
        gradient.angle = token.value;
        haveAngle = true;
        token = this.getToken(true, true);
        if (!token.isSymbol(","))
          return null;
        token = this.getToken(true, true);
      }
      
      else if (token.isIdent("to")) {
        foundPosition = true;
        token = this.getToken(true, true);
        if (token.isIdent("top")
            || token.isIdent("bottom")
            || token.isIdent("left")
            || token.isIdent("right")) {
          gradient.position = token.value;
          token = this.getToken(true, true);
          if (((gradient.position == "top" || gradient.position == "bottom") && (token.isIdent("left") || token.isIdent("right")))
              || ((gradient.position == "left" || gradient.position == "right") && (token.isIdent("top") || token.isIdent("bottom")))) {
            gradient.position += " " + token.value;
            token = this.getToken(true, true);
          }
        }
        else
          return null;
        
        if (!token.isSymbol(","))
          return null;
        token = this.getToken(true, true);
      }
      
      /********** RADIAL **********/
      else if (gradient.isRadial) {
        gradient.shape = "";
        gradient.extent = "";
        gradient.positions = [];
        gradient.at = "";
        
        while (!token.isIdent("at") && !token.isSymbol(",")) {
          if (!gradient.shape
              && (token.isIdent("circle") || token.isIdent("ellipse"))) {
            gradient.shape = token.value;
            token = this.getToken(true, true);
          }
          else if (!gradient.extent
                   && (token.isIdent("closest-corner")
                       || token.isIdent("closes-side")
                       || token.isIdent("farthest-corner")
                       || token.isIdent("farthest-corner"))) {
                     gradient.extent = token.value;
                     token = this.getToken(true, true);
                   }
          else if (gradient.positions.length < 2 && token.isLength()){
            gradient.positions.push(token.value);
            token = this.getToken(true, true);
          }
          else
            break;
        }
        
        // verify if the shape is null of well defined
        if ((gradient.positions.length == 1 && !gradient.extent && (gradient.shape == "circle" || !gradient.shape))
            || (gradient.positions.length == 2 && !gradient.extent && (gradient.shape == "ellipse" || !gradient.shape))
            || (!gradient.positions.length && gradient.extent)
            || (!gradient.positions.length && !gradient.extent)) {
          // shape ok
        }
        else  {
          return null;
        }
        
        if (token.isIdent("at")) {
          token = this.getToken(true, true);
          if (((token.isIdent() && token.value in kPos)
               || token.isDimension()
               || token.isNumber("0")
               || token.isPercentage())) {
            gradient.at = token.value;
            token = this.getToken(true, true);
            if (token.isDimension() || token.isNumber("0") || token.isPercentage()) {
              gradient.at += " " + token.value;
            }
            else if (token.isIdent() && token.value in kPos) {
              if ((gradient.at in kHPos && token.value in kHPos) ||
                  (gradient.at in kVPos && token.value in kVPos))
                return "";
              gradient.at += " " + token.value;
            }
            else {
              this.ungetToken();
              gradient.at += " center";
            }
          }
          else
            return null;
          
          token = this.getToken(true, true);
        }
        
        if (gradient.shape || gradient.extent || gradient.positions.length || gradient.at) {
          if (!token.isSymbol(","))
            return null;
          token = this.getToken(true, true);
        }
      }
      
      // now color stops...
      var stop1 = this.parseColorStop(token);
      if (!stop1)
        return null;
      token = this.currentToken();
      if (!token.isSymbol(","))
        return null;
      token = this.getToken(true, true);
      var stop2 = this.parseColorStop(token);
      if (!stop2)
        return null;
      token = this.currentToken();
      if (token.isSymbol(",")) {
        token = this.getToken(true, true);
      }
      // ok we have at least two color stops
      gradient.stops = [stop1, stop2];
      while (!token.isSymbol(")")) {
        var colorstop = this.parseColorStop(token);
        if (!colorstop)
          return null;
        token = this.currentToken();
        if (!token.isSymbol(")") && !token.isSymbol(","))
          return null;
        if (token.isSymbol(","))
          token = this.getToken(true, true);
        gradient.stops.push(colorstop);
      }
      return gradient;
    }
  }
  return null;
};

CSSParser.prototype.serializeGradient = function(gradient)
{
  var s = gradient.isRadial
  ? (gradient.isRepeating ? "repeating-radial-gradient(" : "radial-gradient(" )
  : (gradient.isRepeating ? "repeating-linear-gradient(" : "linear-gradient(" );
  if (gradient.angle || gradient.position)
    s += (gradient.angle ? gradient.angle: "") +
    (gradient.position ? "to " + gradient.position : "") +
    ", ";
  
  if (gradient.isRadial)
    s += (gradient.shape ? gradient.shape + " " : "") +
    (gradient.extent ? gradient.extent + " " : "") +
    (gradient.positions.length ? gradient.positions.join(" ") + " " : "") +
    (gradient.at ? "at " + gradient.at + " " : "") +
    (gradient.shape || gradient.extent || gradient.positions.length || gradient.at ? ", " : "");
  
  for (var i = 0; i < gradient.stops.length; i++) {
    var colorstop = gradient.stops[i];
    s += colorstop.color + (colorstop.position ? " " + colorstop.position : "");
    if (i != gradient.stops.length -1)
      s += ", ";
  }
  s += ")";
  return s;
};


CSSParser.prototype.parseImportRule = function(aToken, aSheet) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var s = aToken.value;
  this.preserveState();
  var token = this.getToken(true, true);
  var media = [];
  var href = "";
  if (token.isString()) {
    href = token.value;
    s += " " + href;
  }
  else if (token.isFunction("url(")) {
    token = this.getToken(true, true);
    var urlContent = this.parseURL(token);
    if (urlContent) {
      href = "url(" + urlContent;
      s += " " + href;
    }
  }
  else
    this.reportError(kIMPORT_RULE_MISSING_URL);
  
  if (href) {
    token = this.getToken(true, true);
    while (token.isIdent()) {
      s += " " + token.value;
      media.push(token.value);
      token = this.getToken(true, true);
      if (!token)
        break;
      if (token.isSymbol(",")) {
        s += ",";
      } else if (token.isSymbol(";")) {
        break;
      } else
        break;
      token = this.getToken(true, true);
    }
    
    if (!media.length) {
      media.push("all");
    }
    
    if (token.isSymbol(";")) {
      s += ";"
      this.forgetState();
      var rule = new jscsspImportRule();
      rule.currentLine = currentLine;
      rule.parsedCssText = s;
      rule.href = href;
      rule.media = media;
      rule.parentStyleSheet = aSheet;
      aSheet.cssRules.push(rule);
      return true;
    }
  }
  
  this.restoreState();
  this.addUnknownAtRule(aSheet, "@import");
  return false;
};

CSSParser.prototype.parseKeyframesRule = function(aToken, aSheet) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var s = aToken.value;
  var valid = false;
  var keyframesRule = new jscsspKeyframesRule();
  keyframesRule.currentLine = currentLine;
  this.preserveState();
  var token = this.getToken(true, true);
  var foundName = false;
  while (token.isNotNull()) {
    if (token.isIdent()) {
      // should be the keyframes' name
      foundName = true;
      s += " " + token.value;
      keyframesRule.name = token.value;
      token = this.getToken(true, true);
      if (token.isSymbol("{"))
        this.ungetToken();
      else {
        // error...
        token.type = jscsspToken.NULL_TYPE;
        break;
      }
    }
    else if (token.isSymbol("{")) {
      if (!foundName) {
        token.type = jscsspToken.NULL_TYPE;
        // not a valid keyframes at-rule
      }
      break;
    }
    else {
      token.type = jscsspToken.NULL_TYPE;
      // not a valid keyframes at-rule
      break;
    }
    token = this.getToken(true, true);
  }
  
  if (token.isSymbol("{") && keyframesRule.name) {
    // ok let's parse keyframe rules now...
    s += " { ";
    token = this.getToken(true, false);
    while (token.isNotNull()) {
      if (token.isComment() && this.mPreserveComments) {
        s += " " + token.value;
        var comment = new jscsspComment();
        comment.parsedCssText = token.value;
        keyframesRule.cssRules.push(comment);
      } else if (token.isSymbol("}")) {
        valid = true;
        break;
      } else {
        var r = this.parseKeyframeRule(token, keyframesRule, true);
        if (r)
          s += r;
      }
      token = this.getToken(true, false);
    }
  }
  if (valid) {
    this.forgetState();
    keyframesRule.currentLine = currentLine;
    keyframesRule.parsedCssText = s;
    aSheet.cssRules.push(keyframesRule);
    return true;
  }
  this.restoreState();
  return false;
};

CSSParser.prototype.parseKeyframeRule = function(aToken, aOwner) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  this.preserveState();
  var token = aToken;
  
  // find the keyframe keys
  var key = "";
  while (token.isNotNull()) {
    if (token.isIdent() || token.isPercentage()) {
      if (token.isIdent()
          && !token.isIdent("from")
          && !token.isIdent("to")) {
        key = "";
        break;
      }
      key += token.value;
      token = this.getToken(true, true);
      if (token.isSymbol("{")) {
        this.ungetToken();
        break;
      }
      else 
        if (token.isSymbol(",")) {
          key += ", ";
        }
        else {
          key = "";
          break;
        }
    }
    else {
      key = "";
      break;
    }
    token = this.getToken(true, true);
  }
  
  var valid = false;
  var declarations = [];
  if (key) {
    var s = key;
    token = this.getToken(true, true);
    if (token.isSymbol("{")) {
      s += " { ";
      token = this.getToken(true, false);
      while (true) {
        if (!token.isNotNull()) {
          valid = true;
          break;
        }
        if (token.isSymbol("}")) {
          s += "}";
          valid = true;
          break;
        } else {
          var d = this.parseDeclaration(token, declarations, true, true, aOwner);
          s += ((d && declarations.length) ? " " : "") + d;
        }
        token = this.getToken(true, false);
      }
    }
  }
  else {
    // key is invalid so the whole rule is invalid with it
  }
  
  if (valid) {
    var rule = new jscsspKeyframeRule();
    rule.currentLine = currentLine;
    rule.parsedCssText = s;
    rule.declarations = declarations;
    rule.keyText = key;
    rule.parentRule = aOwner;
    aOwner.cssRules.push(rule);
    return s;
  }
  this.restoreState();
  s = this.currentToken().value;
  this.addUnknownAtRule(aOwner, s);
  return "";
};

CSSParser.prototype.parseListStyleShorthand = function(token, aDecl, aAcceptPriority)
{
  var kPosition = { "inside": true, "outside": true };
  
  var lType = null;
  var lPosition = null;
  var lImage = null;
  
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!lType && !lPosition && ! lImage
             && token.isIdent(this.kINHERIT)) {
      lType = this.kINHERIT;
      lPosition = this.kINHERIT;
      lImage = this.kINHERIT;
    }
    
    else if (!lType &&
             (token.isIdent() && token.value in this.kLIST_STYLE_TYPE_NAMES)) {
      lType = token.value;
    }
    
    else if (!lPosition &&
             (token.isIdent() && token.value in kPosition)) {
      lPosition = token.value;
    }
    
    else if (!lImage && token.isFunction("url")) {
      token = this.getToken(true, true);
      var urlContent = this.parseURL(token);
      if (urlContent) {
        lImage = "url(" + urlContent;
      }
      else
        return "";
    }
    else if (!token.isIdent("none"))
      return "";
    
    token = this.getToken(true, true);
  }
  
  // create the declarations
  this.forgetState();
  lType = lType ? lType : "none";
  lImage = lImage ? lImage : "none";
  lPosition = lPosition ? lPosition : "outside";
  
  aDecl.push(this._createJscsspDeclarationFromValue("list-style-type", lType));
  aDecl.push(this._createJscsspDeclarationFromValue("list-style-position", lPosition));
  aDecl.push(this._createJscsspDeclarationFromValue("list-style-image", lImage));
  return lType + " " + lPosition + " " + lImage;
};

CSSParser.prototype.parse = function(aString, aTryToPreserveWhitespaces, aTryToPreserveComments) {
  if (!aString)
    return null; // early way out if we can
  
  this.mPreserveWS       = aTryToPreserveWhitespaces;
  this.mPreserveComments = aTryToPreserveComments;
  this.mPreservedTokens = [];
  this.mScanner.init(aString);
  var sheet = new jscsspStylesheet();
  
  // @charset can only appear at first char of the stylesheet
  var token = this.getToken(false, false);
  if (!token.isNotNull())
    return sheet;
  if (token.isAtRule("@charset")) {
    this.ungetToken();
    this.parseCharsetRule(sheet);
    token = this.getToken(false, false);
  }
  
  var foundStyleRules = false;
  var foundImportRules = false;
  var foundNameSpaceRules = false;
  while (true) {
    if (!token.isNotNull())
      break;
    if (token.isWhiteSpace())
    {
      if (aTryToPreserveWhitespaces)
        this.addWhitespace(sheet, token.value);
    }
    
    else if (token.isComment())
    {
      if (this.mPreserveComments)
        this.addComment(sheet, token.value);
    }
    
    else if (token.isAtRule()) {
      if (token.isAtRule("@variables")) {
        if (!foundImportRules && !foundStyleRules)
          this.parseVariablesRule(token, sheet);
        else {
          this.reportError(kVARIABLES_RULE_POSITION);
          this.addUnknownAtRule(sheet, token.value);
        }
      }
      else if (token.isAtRule("@import")) {
        // @import rules MUST occur before all style and namespace
        // rules
        if (!foundStyleRules && !foundNameSpaceRules)
          foundImportRules = this.parseImportRule(token, sheet);
        else {
          this.reportError(kIMPORT_RULE_POSITION);
          this.addUnknownAtRule(sheet, token.value);
        }
      }
      else if (token.isAtRule("@namespace")) {
        // @namespace rules MUST occur before all style rule and
        // after all @import rules
        if (!foundStyleRules)
          foundNameSpaceRules = this.parseNamespaceRule(token, sheet);
        else {
          this.reportError(kNAMESPACE_RULE_POSITION);
          this.addUnknownAtRule(sheet, token.value);
        }
      }
      else if (token.isAtRule("@font-face")) {
        if (this.parseFontFaceRule(token, sheet))
          foundStyleRules = true;
        else
          this.addUnknownAtRule(sheet, token.value);
      }
      else if (token.isAtRule("@page")) {
        if (this.parsePageRule(token, sheet))
          foundStyleRules = true;
        else
          this.addUnknownAtRule(sheet, token.value);
      }
      else if (token.isAtRule("@media")) {
        if (this.parseMediaRule(token, sheet))
          foundStyleRules = true;
        else
          this.addUnknownAtRule(sheet, token.value);
      }
      else if (token.isAtRule("@keyframes")) {
        if (!this.parseKeyframesRule(token, sheet))
          this.addUnknownAtRule(sheet, token.value);
      }
      else if (token.isAtRule("@charset")) {
        this.reportError(kCHARSET_RULE_CHARSET_SOF);
        this.addUnknownAtRule(sheet, token.value);
      }
      else {
        this.reportError(kUNKNOWN_AT_RULE);
        this.addUnknownAtRule(sheet, token.value);
      }
    }
    
    else // plain style rules
    {
      var ruleText = this.parseStyleRule(token, sheet, false);
      if (ruleText)
        foundStyleRules = true;
    }
    token = this.getToken(false);
  }
  
  return sheet;
};
CSSParser.prototype.parseMarginOrPaddingShorthand = function(token, aDecl, aAcceptPriority, aProperty)
{
  var top = null;
  var bottom = null;
  var left = null;
  var right = null;
  
  var values = [];
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!values.length && token.isIdent(this.kINHERIT)) {
      values.push(token.value);
      token = this.getToken(true, true);
      break;
    }
    
    else if (token.isDimension()
             || token.isNumber("0")
             || token.isPercentage()
             || token.isIdent("auto")) {
      values.push(token.value);
    }
    else
      return "";
    
    token = this.getToken(true, true);
  }
  
  var count = values.length;
  switch (count) {
    case 1:
      top = values[0];
      bottom = top;
      left = top;
      right = top;
      break;
    case 2:
      top = values[0];
      bottom = top;
      left = values[1];
      right = left;
      break;
    case 3:
      top = values[0];
      left = values[1];
      right = left;
      bottom = values[2];
      break;
    case 4:
      top = values[0];
      right = values[1];
      bottom = values[2];
      left = values[3];
      break;
    default:
      return "";
  }
  this.forgetState();
  aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-top", top));
  aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-right", right));
  aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-bottom", bottom));
  aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-left", left));
  return top + " " + right + " " + bottom + " " + left;
};

CSSParser.prototype.parseMediaQuery = function()
{
  var kCONSTRAINTS = {
    "width": true,
    "min-width": true,
    "max-width": true,
    "height": true,
    "min-height": true,
    "max-height": true,
    "device-width": true,
    "min-device-width": true,
    "max-device-width": true,
    "device-height": true,
    "min-device-height": true,
    "max-device-height": true,
    "orientation": true,
    "aspect-ratio": true,
    "min-aspect-ratio": true,
    "max-aspect-ratio": true,
    "device-aspect-ratio": true,
    "min-device-aspect-ratio": true,
    "max-device-aspect-ratio": true,
    "color": true,
    "min-color": true,
    "max-color": true,
    "color-index": true,
    "min-color-index": true,
    "max-color-index": true,
    "monochrome": true,
    "min-monochrome": true,
    "max-monochrome": true,
    "resolution": true,
    "min-resolution": true,
    "max-resolution": true,
    "scan": true,
    "grid": true
  };
  
  var m = {cssText: "", amplifier: "", medium: "", constraints: []};
  var token = this.getToken(true, true);
  
  if (token.isIdent("all") ||
      token.isIdent("aural") ||
      token.isIdent("braille") ||
      token.isIdent("handheld") ||
      token.isIdent("print") ||
      token.isIdent("projection") ||
      token.isIdent("screen") ||
      token.isIdent("tty") ||
      token.isIdent("tv")) {
    m.medium = token.value;
    m.cssText += token.value;
    token = this.getToken(true, true);
  }
  else if (token.isIdent("not") || token.isIdent("only")) {
    m.amplifier = token.value.toLowerCase();
    m.cssText += token.value.toLowerCase();
    token = this.getToken(true, true);
    if (token.isIdent("all") ||
        token.isIdent("aural") ||
        token.isIdent("braille") ||
        token.isIdent("handheld") ||
        token.isIdent("print") ||
        token.isIdent("projection") ||
        token.isIdent("screen") ||
        token.isIdent("tty") ||
        token.isIdent("tv")) {
      m.cssText += " " + token.value;
      m.medium = token.value;
      token = this.getToken(true, true);
    }
    else
      return null;
  }
  
  if (m.medium) {
    if (!token.isNotNull())
      return m;
    if (token.isIdent("and")) {
      m.cssText += " and";
      token = this.getToken(true, true);
    }
    else if (!token.isSymbol("{"))
      return null;
  }
  
  while (token.isSymbol("(")) {
    token = this.getToken(true, true);
    if (token.isIdent() && (token.value in kCONSTRAINTS)) {
      var constraint = token.value;
      token = this.getToken(true, true);
      if (token.isSymbol(":")) {
        token = this.getToken(true, true);
        var values = [];
        while (!token.isSymbol(")")) {
          values.push(token.value);
          token = this.getToken(true, true);
        }
        if (token.isSymbol(")")) {
          m.constraints.push({constraint: constraint, value: values});
          m.cssText += " (" + constraint + ": " + values.join(" ") + ")";
          token = this.getToken(true, true);
          if (token.isNotNull()) {
            if (token.isIdent("and")) {
              m.cssText += " and";
              token = this.getToken(true, true);
            }
            else if (!token.isSymbol("{"))
              return null;
          }
          else
            return m;
        }
        else
          return null;
      }
      else if (token.isSymbol(")")) {
        m.constraints.push({constraint: constraint, value: null});
        m.cssText += " (" + constraint + ")";
        token = this.getToken(true, true);
        if (token.isNotNull()) {
          if (token.isIdent("and")) {
            m.cssText += " and";
            token = this.getToken(true, true);
          }
          else
            return null;
        }
        else
          return m;
      }
      else
        return null;
    }
    else
      return null;
  }
  return m;
};

CSSParser.prototype.parseMediaRule = function(aToken, aSheet) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var s = aToken.value;
  var valid = false;
  var mediaRule = new jscsspMediaRule();
  mediaRule.currentLine = currentLine;
  this.preserveState();
  var token = this.getToken(true, true);
  var foundMedia = false;
  while (token.isNotNull()) {
    this.ungetToken();
    var mediaQuery = this.parseMediaQuery();
    token = this.currentToken();
    if (mediaQuery) {
      foundMedia = true;
      s += " " + mediaQuery.cssText;
      mediaRule.media.push(mediaQuery.cssText);
      if (token.isSymbol(",")) {
        s += ",";
      } else {
        if (token.isSymbol("{"))
          break;
        else {
          // error...
          token.type = jscsspToken.NULL_TYPE;
          break;
        }
      }
    }
    else if (token.isSymbol("{"))
      break;
    else if (foundMedia) {
      token.type = jscsspToken.NULL_TYPE;
      // not a media list
      break;
    }
    
    token = this.getToken(true, true);
  }
  if (token.isSymbol("{") && mediaRule.media.length) {
    // ok let's parse style rules now...
    s += " { ";
    token = this.getToken(true, false);
    while (token.isNotNull()) {
      if (token.isComment()) {
        if (this.mPreserveComments) {
          s += " " + token.value;
          var comment = new jscsspComment();
          comment.parsedCssText = token.value;
          mediaRule.cssRules.push(comment);
        }
      } else if (token.isSymbol("}")) {
        valid = true;
        break;
      } else {
        var r = this.parseStyleRule(token, mediaRule, true);
        if (r)
          s += r;
      }
      token = this.getToken(true, false);
    }
  }
  if (valid) {
    this.forgetState();
    mediaRule.parsedCssText = s;
    aSheet.cssRules.push(mediaRule);
    return true;
  }
  this.restoreState();
  return false;
};

CSSParser.prototype.parseNamespaceRule = function(aToken, aSheet) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var s = aToken.value;
  var valid = false;
  this.preserveState();
  var token = this.getToken(true, true);
  if (token.isNotNull()) {
    var prefix = "";
    var url = "";
    if (token.isIdent()) {
      prefix = token.value;
      s += " " + prefix;
      token = this.getToken(true, true);
    }
    if (token) {
      var foundURL = false;
      if (token.isString()) {
        foundURL = true;
        url = token.value;
        s += " " + url;
      } else if (token.isFunction("url(")) {
        // get a url here...
        token = this.getToken(true, true);
        var urlContent = this.parseURL(token);
        if (urlContent) {
          url += "url(" + urlContent;
          foundURL = true;
          s += " " + urlContent;
        }
      }
    }
    if (foundURL) {
      token = this.getToken(true, true);
      if (token.isSymbol(";")) {
        s += ";";
        this.forgetState();
        var rule = new jscsspNamespaceRule();
        rule.currentLine = currentLine;
        rule.parsedCssText = s;
        rule.prefix = prefix;
        rule.url = url;
        rule.parentStyleSheet = aSheet;
        aSheet.cssRules.push(rule);
        return true;
      }
    }
    
  }
  this.restoreState();
  this.addUnknownAtRule(aSheet, "@namespace");
  return false;
};

CSSParser.prototype.parsePageRule = function(aToken, aSheet) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var s = aToken.value;
  var valid = false;
  var declarations = [];
  this.preserveState();
  var token = this.getToken(true, true);
  var pageSelector = "";
  if (token.isSymbol(":") || token.isIdent()) {
    if (token.isSymbol(":")) {
      pageSelector = ":";
      token = this.getToken(false, false);
    }
    if (token.isIdent()) {
      pageSelector += token.value;
      s += " " + pageSelector;
      token = this.getToken(true, true);
    }
  }
  if (token.isNotNull()) {
    // expecting block start
    if (token.isSymbol("{")) {
      s += " " + token.value;
      var token = this.getToken(true, false);
      while (true) {
        if (token.isSymbol("}")) {
          s += "}";
          valid = true;
          break;
        } else {
          var d = this.parseDeclaration(token, declarations, true, true, aSheet);
          s += ((d && declarations.length) ? " " : "") + d;
        }
        token = this.getToken(true, false);
      }
    }
  }
  if (valid) {
    this.forgetState();
    var rule = new jscsspPageRule();
    rule.currentLine = currentLine;
    rule.parsedCssText = s;
    rule.pageSelector = pageSelector;
    rule.declarations = declarations;
    rule.parentStyleSheet = aSheet;
    aSheet.cssRules.push(rule)
    return true;
  }
  this.restoreState();
  return false;
};

CSSParser.prototype.parsePauseShorthand = function(token, declarations, aAcceptPriority)
{
  var before = "";
  var after = "";
  
  var values = [];
  var values = [];
  while (true) {
    
    if (!token.isNotNull())
      break;
    
    if (token.isSymbol(";")
        || (aAcceptPriority && token.isSymbol("!"))
        || token.isSymbol("}")) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    else if (!values.length && token.isIdent(this.kINHERIT)) {
      values.push(token.value);
    }
    
    else if (token.isDimensionOfUnit("ms")
             || token.isDimensionOfUnit("s")
             || token.isPercentage()
             || token.isNumber("0"))
      values.push(token.value);
    else
      return "";
    
    token = this.getToken(true, true);
  }
  
  var count = values.length;
  switch (count) {
    case 1:
      before = values[0];
      after = before;
      break;
    case 2:
      before = values[0];
      after = values[1];
      break;
    default:
      return "";
  }
  this.forgetState();
  aDecl.push(this._createJscsspDeclarationFromValue("pause-before", before));
  aDecl.push(this._createJscsspDeclarationFromValue("pause-after", after));
  return before + " " + after;
};

CSSParser.prototype.parseDefaultPropertyValue = function(token, aDecl, aAcceptPriority, descriptor, aSheet) {
  var valueText = "";
  var blocks = [];
  var foundPriority = false;
  var values = [];
  while (token.isNotNull()) {
    
    if ((token.isSymbol(";")
         || token.isSymbol("}")
         || token.isSymbol("!"))
        && !blocks.length) {
      if (token.isSymbol("}"))
        this.ungetToken();
      break;
    }
    
    if (token.isIdent(this.kINHERIT)) {
      if (values.length) {
        return "";
      }
      else {
        valueText = this.kINHERIT;
        var value = new jscsspVariable(kJscsspINHERIT_VALUE, aSheet);
        values.push(value);
        token = this.getToken(true, true);
        break;
      }
    }
    else if (token.isSymbol("{")
             || token.isSymbol("(")
             || token.isSymbol("[")) {
      blocks.push(token.value);
    }
    else if (token.isSymbol("}")
             || token.isSymbol("]")) {
      if (blocks.length) {
        var ontop = blocks[blocks.length - 1];
        if ((token.isSymbol("}") && ontop == "{")
            || (token.isSymbol(")") && ontop == "(")
            || (token.isSymbol("]") && ontop == "[")) {
          blocks.pop();
        }
      }
    }
    // XXX must find a better way to store individual values
    // probably a |values: []| field holding dimensions, percentages
    // functions, idents, numbers and symbols, in that order.
    if (token.isFunction()) {
      if (token.isFunction("var(")) {
        token = this.getToken(true, true);
        if (token.isIdent()) {
          var name = token.value;
          token = this.getToken(true, true);
          if (token.isSymbol(")")) {
            var value = new jscsspVariable(kJscsspVARIABLE_VALUE, aSheet);
            valueText += "var(" + name + ")";
            value.name = name;
            values.push(value);
          }
          else
            return "";
        }
        else
          return "";
      }
      else {
        var fn = token.value;
        token = this.getToken(false, true);
        var arg = this.parseFunctionArgument(token);
        if (arg) {
          valueText += fn + arg;
          var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet);
          value.value = fn + arg;
          values.push(value);
        }
        else
          return "";
      }
    }
    else if (token.isSymbol("#")) {
      var color = this.parseColor(token);
      if (color) {
        valueText += color;
        var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet);
        value.value = color;
        values.push(value);
      }
      else
        return "";
    }
    else if (!token.isWhiteSpace() && !token.isSymbol(",")) {
      var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet);
      value.value = token.value;
      values.push(value);
      valueText += token.value;
    }
    else
      valueText += token.value;
    token = this.getToken(false, true);
  }
  if (values.length && valueText) {
    this.forgetState();
    aDecl.push(this._createJscsspDeclarationFromValuesArray(descriptor, values, valueText));
    return valueText;
  }
  return "";
};

CSSParser.prototype.parseSelector = function(aToken, aParseSelectorOnly) {
  var s = "";
  var specificity = {a: 0, b: 0, c: 0, d: 0}; // CSS 2.1 section 6.4.3
  var isFirstInChain = true;
  var token = aToken;
  var valid = false;
  var combinatorFound = false;
  while (true) {
    if (!token.isNotNull()) {
      if (aParseSelectorOnly)
        return {selector: s, specificity: specificity };
      return "";
    }
    
    if (!aParseSelectorOnly && token.isSymbol("{")) {
      // end of selector
      valid = !combinatorFound;
      // don't unget if invalid since addUnknownRule is going to restore state anyway
      if (valid)
        this.ungetToken();
      break;
    }
    
    if (token.isSymbol(",")) { // group of selectors
      s += token.value;
      isFirstInChain = true;
      combinatorFound = false;
      token = this.getToken(false, true);
      continue;
    }
    // now combinators and grouping...
    else if (!combinatorFound
             && (token.isWhiteSpace()
                 || token.isSymbol(">")
                 || token.isSymbol("+")
                 || token.isSymbol("~"))) {
               if (token.isWhiteSpace()) {
                 s += " ";
                 var nextToken = this.lookAhead(true, true);
                 if (!nextToken.isNotNull()) {
                   if (aParseSelectorOnly)
                     return {selector: s, specificity: specificity };
                   return "";
                 }
                 if (nextToken.isSymbol(">")
                     || nextToken.isSymbol("+")
                     || nextToken.isSymbol("~")) {
                   token = this.getToken(true, true);
                   s += token.value + " ";
                   combinatorFound = true;
                 }
               }
               else {
                 s += token.value;
                 combinatorFound = true;
               }
               isFirstInChain = true;
               token = this.getToken(true, true);
               continue;
             }
    else {
      var simpleSelector = this.parseSimpleSelector(token, isFirstInChain, true);
      if (!simpleSelector)
        break; // error
      s += simpleSelector.selector;
      specificity.b += simpleSelector.specificity.b;
      specificity.c += simpleSelector.specificity.c;
      specificity.d += simpleSelector.specificity.d;
      isFirstInChain = false;
      combinatorFound = false;
    }
    
    token = this.getToken(false, true);
  }
  
  if (valid) {
    return {selector: s, specificity: specificity };
  }
  return "";
};

CSSParser.prototype.isPseudoElement = function(aIdent)
{
  switch (aIdent) {
    case "first-letter":
    case "first-line":
    case "before":
    case "after":
    case "marker":
      return true;
      break;
    default: 
      break;
  }
  return false;
};

CSSParser.prototype.parseSimpleSelector = function(token, isFirstInChain, canNegate)
{
  var s = "";
  var specificity = {a: 0, b: 0, c: 0, d: 0}; // CSS 2.1 section 6.4.3
  
  if (isFirstInChain
      && (token.isSymbol("*") || token.isSymbol("|") || token.isIdent())) {
    // type or universal selector
    if (token.isSymbol("*") || token.isIdent()) {
      // we don't know yet if it's a prefix or a universal
      // selector
      s += token.value;
      var isIdent = token.isIdent();
      token = this.getToken(false, true);
      if (token.isSymbol("|")) {
        // it's a prefix
        s += token.value;
        token = this.getToken(false, true);
        if (token.isIdent() || token.isSymbol("*")) {
          // ok we now have a type element or universal
          // selector
          s += token.value;
          if (token.isIdent())
            specificity.d++;
        } else
          // oops that's an error...
          return null;
      } else {
        this.ungetToken();
        if (isIdent)
          specificity.d++;
      }
    } else if (token.isSymbol("|")) {
      s += token.value;
      token = this.getToken(false, true);
      if (token.isIdent() || token.isSymbol("*")) {
        s += token.value;
        if (token.isIdent())
          specificity.d++;
      } else
        // oops that's an error
        return null;
    }
  }
  
  else if (token.isSymbol(".") || token.isSymbol("#")) {
    var isClass = token.isSymbol(".");
    s += token.value;
    token = this.getToken(false, true);
    if (token.isIdent()) {
      s += token.value;
      if (isClass)
        specificity.c++;
      else
        specificity.b++;
    }
    else
      return null;
  }
  
  else if (token.isSymbol(":")) {
    s += token.value;
    token = this.getToken(false, true);
    if (token.isSymbol(":")) {
      s += token.value;
      token = this.getToken(false, true);
    }
    if (token.isIdent()) {
      s += token.value;
      if (this.isPseudoElement(token.value))
        specificity.d++;
      else
        specificity.c++;
    }
    else if (token.isFunction()) {
      s += token.value;
      if (token.isFunction(":not(")) {
        if (!canNegate)
          return null;
        token = this.getToken(true, true);
        var simpleSelector = this.parseSimpleSelector(token, isFirstInChain, false);
        if (!simpleSelector)
          return null;
        else {
          s += simpleSelector.selector;
          token = this.getToken(true, true);
          if (token.isSymbol(")"))
            s += ")";
          else
            return null;
        }
        specificity.c++;
      }
      else {
        while (true) {
          token = this.getToken(false, true);
          if (token.isSymbol(")")) {
            s += ")";
            break;
          } else
            s += token.value;
        }
        specificity.c++;
      }
    } else
      return null;
    
  } else if (token.isSymbol("[")) {
    s += "[";
    token = this.getToken(true, true);
    if (token.isIdent() || token.isSymbol("*")) {
      s += token.value;
      var nextToken = this.getToken(true, true);
      if (nextToken.isSymbol("|")) {
        s += "|";
        token = this.getToken(true, true);
        if (token.isIdent())
          s += token.value;
        else
          return null;
      } else
        this.ungetToken();
    } else if (token.isSymbol("|")) {
      s += "|";
      token = this.getToken(true, true);
      if (token.isIdent())
        s += token.value;
      else
        return null;
    }
    else
      return null;
    
    // nothing, =, *=, $=, ^=, |=
    token = this.getToken(true, true);
    if (token.isIncludes()
        || token.isDashmatch()
        || token.isBeginsmatch()
        || token.isEndsmatch()
        || token.isContainsmatch()
        || token.isSymbol("=")) {
      s += token.value;
      token = this.getToken(true, true);
      if (token.isString() || token.isIdent()) {
        s += token.value;
        token = this.getToken(true, true);
      }
      else
        return null;
      
      if (token.isSymbol("]")) {
        s += token.value;
        specificity.c++;
      }
      else
        return null;
    }
    else if (token.isSymbol("]")) {
      s += token.value;
      specificity.c++;
    }
    else
      return null;
    
  }
  else if (token.isWhiteSpace()) {
    var t = this.lookAhead(true, true);
    if (t.isSymbol('{'))
      return ""
      }
  if (s)
    return {selector: s, specificity: specificity };
  return null;
};

CSSParser.prototype.trim11 = function(str) {
  str = str.replace(/^\s+/, '');
  for (var i = str.length - 1; i >= 0; i--) {
    if (/\S/.test( str.charAt(i) )) { // XXX charat
      str = str.substring(0, i + 1);
      break;
    }
  }
  return str;
};

CSSParser.prototype.parseStyleRule = function(aToken, aOwner, aIsInsideMediaRule)
{
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  this.preserveState();
  // first let's see if we have a selector here...
  var selector = this.parseSelector(aToken, false);
  var valid = false;
  var declarations = [];
  if (selector) {
    selector = this.trim11(selector.selector);
    var s = selector;
    var token = this.getToken(true, true);
    if (token.isSymbol("{")) {
      s += " { ";
      var token = this.getToken(true, false);
      while (true) {
        if (!token.isNotNull()) {
          valid = true;
          break;
        }
        if (token.isSymbol("}")) {
          s += "}";
          valid = true;
          break;
        } else {
          var d = this.parseDeclaration(token, declarations, true, true, aOwner);
          s += ((d && declarations.length) ? " " : "") + d;
        }
        token = this.getToken(true, false);
      }
    }
  }
  else {
    // selector is invalid so the whole rule is invalid with it
  }
  
  if (valid) {
    var rule = new jscsspStyleRule();
    rule.currentLine = currentLine;
    rule.parsedCssText = s;
    rule.declarations = declarations;
    rule.mSelectorText = selector;
    if (aIsInsideMediaRule)
      rule.parentRule = aOwner;
    else
      rule.parentStyleSheet = aOwner;
    aOwner.cssRules.push(rule);
    return s;
  }
  this.restoreState();
  s = this.currentToken().value;
  this.addUnknownAtRule(aOwner, s);
  return "";
};

CSSParser.prototype.parseTextShadows = function()
{
  var shadows = [];
  var token = this.getToken(true, true);
  var color = "", blurRadius = "0px", offsetX = "0px", offsetY = "0px"; 
  while (token.isNotNull()) {
    if (token.isIdent("none")) {
      shadows.push( { none: true } );
      token = this.getToken(true, true);
    }
    else {
      if (token.isFunction("rgb(") ||
          token.isFunction("rgba(") ||
          token.isFunction("hsl(") ||
          token.isFunction("hsla(") ||
          token.isSymbol("#") ||
          token.isIdent()) {
        var color = this.parseColor(token);
        token = this.getToken(true, true);
      }
      if (token.isPercentage() ||
          token.isDimensionOfUnit("cm") ||
          token.isDimensionOfUnit("mm") ||
          token.isDimensionOfUnit("in") ||
          token.isDimensionOfUnit("pc") ||
          token.isDimensionOfUnit("px") ||
          token.isDimensionOfUnit("em") ||
          token.isDimensionOfUnit("ex") ||
          token.isDimensionOfUnit("pt")) {
        var offsetX = token.value;
        token = this.getToken(true, true);
      }
      else
        return [];
      if (token.isPercentage() ||
          token.isDimensionOfUnit("cm") ||
          token.isDimensionOfUnit("mm") ||
          token.isDimensionOfUnit("in") ||
          token.isDimensionOfUnit("pc") ||
          token.isDimensionOfUnit("px") ||
          token.isDimensionOfUnit("em") ||
          token.isDimensionOfUnit("ex") ||
          token.isDimensionOfUnit("pt")) {
        var offsetY = token.value;
        token = this.getToken(true, true);
      }
      else
        return [];
      if (token.isPercentage() ||
          token.isDimensionOfUnit("cm") ||
          token.isDimensionOfUnit("mm") ||
          token.isDimensionOfUnit("in") ||
          token.isDimensionOfUnit("pc") ||
          token.isDimensionOfUnit("px") ||
          token.isDimensionOfUnit("em") ||
          token.isDimensionOfUnit("ex") ||
          token.isDimensionOfUnit("pt")) {
        var blurRadius = token.value;
        token = this.getToken(true, true);
      }
      if (!color &&
          (token.isFunction("rgb(") ||
           token.isFunction("rgba(") ||
           token.isFunction("hsl(") ||
           token.isFunction("hsla(") ||
           token.isSymbol("#") ||
           token.isIdent())) {
            var color = this.parseColor(token);
            token = this.getToken(true, true);
          }
      
      shadows.push( { none: false,
                   color: color,
                   offsetX: offsetX, offsetY: offsetY,
                   blurRadius: blurRadius } );
      
      if (token.isSymbol(",")) {
        color = "";
        blurRadius = "0px";
        offsetX = "0px";
        offsetY = "0px"; 
        token = this.getToken(true, true);
      }
      else if (!token.isNotNull())
        return shadows;
      else
        return [];
    }
  }
  return shadows;
};

CSSParser.prototype.currentToken = function() {
  return this.mToken;
};

CSSParser.prototype.getHexValue = function() {
  this.mToken = this.mScanner.nextHexValue();
  return this.mToken;
};

CSSParser.prototype.getToken = function(aSkipWS, aSkipComment) {
  if (this.mLookAhead) {
    this.mToken = this.mLookAhead;
    this.mLookAhead = null;
    return this.mToken;
  }
  
  this.mToken = this.mScanner.nextToken();
  while (this.mToken &&
         ((aSkipWS && this.mToken.isWhiteSpace()) ||
          (aSkipComment && this.mToken.isComment())))
    this.mToken = this.mScanner.nextToken();
  return this.mToken;
};

CSSParser.prototype.lookAhead = function(aSkipWS, aSkipComment) {
  var preservedToken = this.mToken;
  this.mScanner.preserveState();
  var token = this.getToken(aSkipWS, aSkipComment);
  this.mScanner.restoreState();
  this.mToken = preservedToken;
  
  return token;
};

CSSParser.prototype.ungetToken = function() {
  this.mLookAhead = this.mToken;
};

CSSParser.prototype.addWhitespace = function(aSheet, aString) {
  var rule = new jscsspWhitespace();
  rule.parsedCssText = aString;
  rule.parentStyleSheet = aSheet;
  aSheet.cssRules.push(rule);
};

CSSParser.prototype.addComment = function(aSheet, aString) {
  var rule = new jscsspComment();
  rule.parsedCssText = aString;
  rule.parentStyleSheet = aSheet;
  aSheet.cssRules.push(rule);
};

CSSParser.prototype._createJscsspDeclaration = function(property, value)
{
  var decl = new jscsspDeclaration();
  decl.property = property;
  decl.value = this.trim11(value);
  decl.parsedCssText = property + ": " + value + ";";
  return decl;
};

CSSParser.prototype._createJscsspDeclarationFromValue = function(property, valueText)
{
  var decl = new jscsspDeclaration();
  decl.property = property;
  var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null);
  value.value = valueText;
  decl.values = [value];
  decl.valueText = valueText;
  decl.parsedCssText = property + ": " + valueText + ";";
  return decl;
};

CSSParser.prototype._createJscsspDeclarationFromValuesArray = function(property, values, valueText)
{
  var decl = new jscsspDeclaration();
  decl.property = property;
  decl.values = values;
  decl.valueText = valueText;
  decl.parsedCssText = property + ": " + valueText + ";";
  return decl;
};

CSSParser.prototype.preserveState = function() {
  this.mPreservedTokens.push(this.currentToken());
  this.mScanner.preserveState();
};

CSSParser.prototype.restoreState = function() {
  if (this.mPreservedTokens.length) {
    this.mScanner.restoreState();
    this.mToken = this.mPreservedTokens.pop();
  }
};

CSSParser.prototype.forgetState = function() {
  if (this.mPreservedTokens.length) {
    this.mScanner.forgetState();
    this.mPreservedTokens.pop();
  }
};

CSSParser.prototype.addUnknownAtRule = function(aSheet, aString) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var blocks = [];
  var token = this.getToken(false, false);
  while (token.isNotNull()) {
    aString += token.value;
    if (token.isSymbol(";") && !blocks.length)
      break;
    else if (token.isSymbol("{")
             || token.isSymbol("(")
             || token.isSymbol("[")
             || token.type == "function") {
      blocks.push(token.isFunction() ? "(" : token.value);
    } else if (token.isSymbol("}")
               || token.isSymbol(")")
               || token.isSymbol("]")) {
      if (blocks.length) {
        var ontop = blocks[blocks.length - 1];
        if ((token.isSymbol("}") && ontop == "{")
            || (token.isSymbol(")") && ontop == "(")
            || (token.isSymbol("]") && ontop == "[")) {
          blocks.pop();
          if (!blocks.length && token.isSymbol("}"))
            break;
        }
      }
    }
    token = this.getToken(false, false);
  }
  
  this.addUnknownRule(aSheet, aString, currentLine);
};

CSSParser.prototype.addUnknownRule = function(aSheet, aString, aCurrentLine) {
  var errorMsg = this.consumeError();
  var rule = new jscsspErrorRule(errorMsg);
  rule.currentLine = aCurrentLine;
  rule.parsedCssText = aString;
  rule.parentStyleSheet = aSheet;
  aSheet.cssRules.push(rule);
};

CSSParser.prototype.parseURL = function(token)
{
  var value = "";
  if (token.isString())
  {
    value += token.value;
    token = this.getToken(true, true);
  }
  else
    while (true)
    {
      if (!token.isNotNull()) {
        this.reportError(kURL_EOF);
        return "";
      }
      if (token.isWhiteSpace()) {
        nextToken = this.lookAhead(true, true);
        // if next token is not a closing parenthesis, that's an error
        if (!nextToken.isSymbol(")")) {
          this.reportError(kURL_WS_INSIDE);
          token = this.currentToken();
          break;
        }
      }
      if (token.isSymbol(")")) {
        break;
      }
      value += token.value;
      token = this.getToken(false, false);
    }
  
  if (token.isSymbol(")")) {
    return value + ")";
  }
  return "";
};

CSSParser.prototype.parseVariablesRule = function(token, aSheet) {
  var currentLine = CountLF(this.mScanner.getAlreadyScanned());
  var s = token.value;
  var declarations = [];
  var valid = false;
  this.preserveState();
  token = this.getToken(true, true);
  var media = [];
  var foundMedia = false;
  while (token.isNotNull()) {
    if (token.isIdent()) {
      foundMedia = true;
      s += " " + token.value;
      media.push(token.value);
      token = this.getToken(true, true);
      if (token.isSymbol(",")) {
        s += ",";
      } else {
        if (token.isSymbol("{"))
          this.ungetToken();
        else {
          // error...
          token.type = jscsspToken.NULL_TYPE;
          break;
        }
      }
    } else if (token.isSymbol("{"))
      break;
    else if (foundMedia) {
      token.type = jscsspToken.NULL_TYPE;
      // not a media list
      break;
    }
    token = this.getToken(true, true);
  }
  
  if (token.isSymbol("{")) {
    s += " {";
    token = this.getToken(true, true);
    while (true) {
      if (!token.isNotNull()) {
        valid = true;
        break;
      }
      if (token.isSymbol("}")) {
        s += "}";
        valid = true;
        break;
      } else {
        var d = this.parseDeclaration(token, declarations, true, false, aSheet);
        s += ((d && declarations.length) ? " " : "") + d;
      }
      token = this.getToken(true, false);
    }
  }
  if (valid) {
    this.forgetState();
    var rule = new jscsspVariablesRule();
    rule.currentLine = currentLine;
    rule.parsedCssText = s;
    rule.declarations = declarations;
    rule.media = media;
    rule.parentStyleSheet = aSheet;
    aSheet.cssRules.push(rule)
    return true;
  }
  this.restoreState();
  return false;
};

function jscsspToken(aType, aValue, aUnit)
{
  this.type = aType;
  this.value = aValue;
  this.unit = aUnit;
}

jscsspToken.NULL_TYPE = 0;

jscsspToken.WHITESPACE_TYPE = 1;
jscsspToken.STRING_TYPE = 2;
jscsspToken.COMMENT_TYPE = 3;
jscsspToken.NUMBER_TYPE = 4;
jscsspToken.IDENT_TYPE = 5;
jscsspToken.FUNCTION_TYPE = 6;
jscsspToken.ATRULE_TYPE = 7;
jscsspToken.INCLUDES_TYPE = 8;
jscsspToken.DASHMATCH_TYPE = 9;
jscsspToken.BEGINSMATCH_TYPE = 10;
jscsspToken.ENDSMATCH_TYPE = 11;
jscsspToken.CONTAINSMATCH_TYPE = 12;
jscsspToken.SYMBOL_TYPE = 13;
jscsspToken.DIMENSION_TYPE = 14;
jscsspToken.PERCENTAGE_TYPE = 15;
jscsspToken.HEX_TYPE = 16;

jscsspToken.prototype = {

  isNotNull: function ()
  {
    return this.type;
  },

  _isOfType: function (aType, aValue)
  {
    return (this.type == aType && (!aValue || this.value.toLowerCase() == aValue));
  },

  isWhiteSpace: function(w)
  {
    return this._isOfType(jscsspToken.WHITESPACE_TYPE, w);
  },

  isString: function()
  {
    return this._isOfType(jscsspToken.STRING_TYPE);
  },

  isComment: function()
  {
    return this._isOfType(jscsspToken.COMMENT_TYPE);
  },

  isNumber: function(n)
  {
    return this._isOfType(jscsspToken.NUMBER_TYPE, n);
  },

  isIdent: function(i)
  {
    return this._isOfType(jscsspToken.IDENT_TYPE, i);
  },

  isFunction: function(f)
  {
    return this._isOfType(jscsspToken.FUNCTION_TYPE, f);
  },

  isAtRule: function(a)
  {
    return this._isOfType(jscsspToken.ATRULE_TYPE, a);
  },

  isIncludes: function()
  {
    return this._isOfType(jscsspToken.INCLUDES_TYPE);
  },

  isDashmatch: function()
  {
    return this._isOfType(jscsspToken.DASHMATCH_TYPE);
  },

  isBeginsmatch: function()
  {
    return this._isOfType(jscsspToken.BEGINSMATCH_TYPE);
  },

  isEndsmatch: function()
  {
    return this._isOfType(jscsspToken.ENDSMATCH_TYPE);
  },

  isContainsmatch: function()
  {
    return this._isOfType(jscsspToken.CONTAINSMATCH_TYPE);
  },

  isSymbol: function(c)
  {
    return this._isOfType(jscsspToken.SYMBOL_TYPE, c);
  },

  isDimension: function()
  {
    return this._isOfType(jscsspToken.DIMENSION_TYPE);
  },

  isPercentage: function()
  {
    return this._isOfType(jscsspToken.PERCENTAGE_TYPE);
  },

  isHex: function()
  {
    return this._isOfType(jscsspToken.HEX_TYPE);
  },

  isDimensionOfUnit: function(aUnit)
  {
    return (this.isDimension() && this.unit == aUnit);
  },

  isLength: function()
  {
    return (this.isPercentage() ||
            this.isDimensionOfUnit("cm") ||
            this.isDimensionOfUnit("mm") ||
            this.isDimensionOfUnit("in") ||
            this.isDimensionOfUnit("pc") ||
            this.isDimensionOfUnit("px") ||
            this.isDimensionOfUnit("em") ||
            this.isDimensionOfUnit("ex") ||
            this.isDimensionOfUnit("pt"));
  },

  isAngle: function()
  {
    return (this.isDimensionOfUnit("deg") ||
            this.isDimensionOfUnit("rad") ||
            this.isDimensionOfUnit("grad"));
  }
}

/* kJscsspCHARSET_RULE */

function jscsspCharsetRule()
{
  this.type = kJscsspCHARSET_RULE;
  this.encoding = null;
  this.parsedCssText = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspCharsetRule.prototype = {

  cssText: function() {
    return "@charset " + this.encoding + ";";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    if (parser.parseCharsetRule(sheet)) {
      var newRule = sheet.cssRules[0];
      this.encoding = newRule.encoding;
      this.parsedCssText = newRule.parsedCssText;
      return;
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspCOMMENT */

function jscsspComment()
{
  this.type = kJscsspCOMMENT;
  this.parsedCssText = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspComment.prototype = {
  cssText: function() {
    return this.parsedCssText;
  },

  setCssText: function(val) {
    var parser = new CSSParser(val);
    var token = parser.getToken(true, false);
    if (token.isComment())
      this.parsedCssText = token.value;
    else
      throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspSTYLE_DECLARATION */

function jscsspDeclaration()
{
  this.type = kJscsspSTYLE_DECLARATION;
  this.property = null;
  this.values = [];
  this.valueText = null;
  this.priority = null;
  this.parsedCssText = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspDeclaration.prototype = {
  kCOMMA_SEPARATED: {
    "cursor": true,
    "font-family": true,
    "voice-family": true,
    "background-image": true
  },

  kUNMODIFIED_COMMA_SEPARATED_PROPERTIES: {
    "text-shadow": true,
    "box-shadow": true,
    "-moz-transition": true,
    "-moz-transition-property": true,
    "-moz-transition-duration": true,
    "-moz-transition-timing-function": true,
    "-moz-transition-delay": true,
    "src": true,
    "-moz-font-feature-settings": true
  },

  cssText: function() {
    var prefixes = PrefixHelper.prefixesForProperty(this.property);

    var rv = "";
    if (this.property in this.kUNMODIFIED_COMMA_SEPARATED_PROPERTIES) {
      if (prefixes) {
        rv = "";
        for (var propertyIndex = 0; propertyIndex < prefixes.length; propertyIndex++) {
          var property = prefixes[propertyIndex];
          rv += (propertyIndex ? gTABS : "") + property + ": ";
          rv += this.valueText + (this.priority ? " !important" : "") + ";";
          rv += ((prefixes.length > 1 && propertyIndex != prefixes.length -1) ? "\n" : "");
        }
        return rv;
      }
      return this.property + ": " + this.valueText +
             (this.priority ? " !important" : "") + ";"
    }

    if (prefixes) {
      rv = "";
      for (var propertyIndex = 0; propertyIndex < prefixes.length; propertyIndex++) {
        var property = prefixes[propertyIndex];
        rv += (propertyIndex ? gTABS : "") + property + ": ";
        var separator = (property in this.kCOMMA_SEPARATED) ? ", " : " ";
        for (var i = 0; i < this.values.length; i++)
          if (this.values[i].cssText() != null)
            rv += (i ? separator : "") + this.values[i].cssText();
          else
            return null;
        rv += (this.priority ? " !important" : "") + ";" +
              ((prefixes.length > 1 && propertyIndex != prefixes.length -1) ? "\n" : "");
      }
      return rv;
    }

    var separator = (this.property in this.kCOMMA_SEPARATED) ? ", " : " ";
    var extras = {"webkit": false, "presto": false, "trident": false, "gecko1.9.2": false, "generic": false }
    for (var i = 0; i < this.values.length; i++) {
      var v = this.values[i].cssText();
      if (v != null) {
        var paren = v.indexOf("(");
        var kwd = v;
        if (paren != -1)
          kwd = v.substr(0, paren);
        if (kwd in kCSS_VENDOR_VALUES) {
          for (var j in kCSS_VENDOR_VALUES[kwd]) {
            extras[j] = extras[j] || (kCSS_VENDOR_VALUES[kwd][j] != "");
          }
        }
      }
      else
        return null;
    }

    for (var j in extras) {
      if (extras[j]) {
        var str = "\n" + gTABS +  this.property + ": ";
        for (var i = 0; i < this.values.length; i++) {
          var v = this.values[i].cssText();
          if (v != null) {
            var paren = v.indexOf("(");
            var kwd = v;
            if (paren != -1)
              kwd = v.substr(0, paren);
            if (kwd in kCSS_VENDOR_VALUES) {
              functor = kCSS_VENDOR_VALUES[kwd][j];
              if (functor) {
                v = (typeof functor == "string") ? functor : functor(v, j);
                if (!v) {
                  str = null;
                  break;
                }
              }
            }
            str += (i ? separator : "") + v;
          }
          else
            return null;
        }
        if (str)
          rv += str + ";"
        else
          rv += "\n" + gTABS + "/* Impossible to translate property " + this.property + " for " + j + " */";
      }
    }

    rv += "\n" + gTABS + this.property + ": ";
    for (var i = 0; i < this.values.length; i++) {
      var v = this.values[i].cssText();
      if (v != null) {
        rv += (i ? separator : "") + v;
      }
    }
    rv += (this.priority ? " !important" : "") + ";";

    return rv;
  },

  setCssText: function(val) {
    var declarations = [];
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (parser.parseDeclaration(token, declarations, true, true, null)
        && declarations.length
        && declarations[0].type == kJscsspSTYLE_DECLARATION) {
      var newDecl = declarations.cssRules[0];
      this.property = newDecl.property;
      this.value = newDecl.value;
      this.priority = newDecl.priority;
      this.parsedCssText = newRule.parsedCssText;
      return;
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspUNKNOWN_RULE */

function jscsspErrorRule(aErrorMsg)
{
  this.error = aErrorMsg ? aErrorMsg : "INVALID"; 
  this.type = kJscsspUNKNOWN_RULE;
  this.parsedCssText = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspErrorRule.prototype = {
  cssText: function() {
    return this.parsedCssText;
  }
};

/* kJscsspFONT_FACE_RULE */

function jscsspFontFaceRule()
{
  this.type = kJscsspFONT_FACE_RULE;
  this.parsedCssText = null;
  this.descriptors = [];
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspFontFaceRule.prototype = {
  cssText: function() {
    var rv = gTABS + "@font-face {\n";
    var preservedGTABS = gTABS;
    gTABS += "  ";
    for (var i = 0; i < this.descriptors.length; i++)
      rv += gTABS + this.descriptors[i].cssText() + "\n";
    gTABS = preservedGTABS;
    return rv + gTABS + "}";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (token.isAtRule("@font-face")) {
      if (parser.parseFontFaceRule(token, sheet)) {
        var newRule = sheet.cssRules[0];
        this.descriptors = newRule.descriptors;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspIMPORT_RULE */

function jscsspImportRule()
{
  this.type = kJscsspIMPORT_RULE;
  this.parsedCssText = null;
  this.href = null;
  this.media = []; 
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspImportRule.prototype = {
  cssText: function() {
    var mediaString = this.media.join(", ");
    return "@import " + this.href
                      + ((mediaString && mediaString != "all") ? mediaString + " " : "")
                      + ";";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (token.isAtRule("@import")) {
      if (parser.parseImportRule(token, sheet)) {
        var newRule = sheet.cssRules[0];
        this.href = newRule.href;
        this.media = newRule.media;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspKEYFRAME_RULE */
function jscsspKeyframeRule()
{
  this.type = kJscsspKEYFRAME_RULE;
  this.parsedCssText = null;
  this.declarations = []
  this.keyText = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspKeyframeRule.prototype = {
  cssText: function() {
    var rv = this.keyText + " {\n";
    var preservedGTABS = gTABS;
    gTABS += "  ";
    for (var i = 0; i < this.declarations.length; i++) {
      var declText = this.declarations[i].cssText();
      if (declText)
        rv += gTABS + this.declarations[i].cssText() + "\n";
    }
    gTABS = preservedGTABS;
    return rv + gTABS + "}";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (!token.isNotNull()) {
      if (parser.parseKeyframeRule(token, sheet, false)) {
        var newRule = sheet.cssRules[0];
        this.keyText = newRule.keyText;
        this.declarations = newRule.declarations;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspKEYFRAMES_RULE */
function jscsspKeyframesRule()
{
  this.type = kJscsspKEYFRAMES_RULE;
  this.parsedCssText = null;
  this.cssRules = [];
  this.name = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspKeyframesRule.prototype = {
  cssText: function() {
    var rv = "";
    var prefixes = ["moz", "webkit", "ms", "o", ""];
    for (var p = 0; p < prefixes.length; p++) {
      rv += gTABS
            + "@" + (prefixes[p] ? "-" + prefixes[p] + "-" : "") + "keyframes "
            + this.name + " {\n";
      var preservedGTABS = gTABS;
      gTABS += "  ";
      for (var i = 0; i < this.cssRules.length; i++)
        rv += gTABS + this.cssRules[i].cssText() + "\n";
      gTABS = preservedGTABS;
      rv += gTABS + "}\n\n";
    }
    return rv;
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (token.isAtRule("@-mozkeyframes")) {
      if (parser.parseKeyframesRule(token, sheet)) {
        var newRule = sheet.cssRules[0];
        this.cssRules = newRule.cssRules;
        this.name = newRule.name;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspMEDIA_RULE */

function jscsspMediaRule()
{
  this.type = kJscsspMEDIA_RULE;
  this.parsedCssText = null;
  this.cssRules = [];
  this.media = [];
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspMediaRule.prototype = {
  cssText: function() {
    var rv = gTABS + "@media " + this.media.join(", ") + " {\n";
    var preservedGTABS = gTABS;
    gTABS += "  ";
    for (var i = 0; i < this.cssRules.length; i++)
      rv += this.cssRules[i].cssText() + "\n";
    gTABS = preservedGTABS;
    return rv + gTABS + "}";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (token.isAtRule("@media")) {
      if (parser.parseMediaRule(token, sheet)) {
        var newRule = sheet.cssRules[0];
        this.cssRules = newRule.cssRules;
        this.media = newRule.media;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspNAMESPACE_RULE */

function jscsspNamespaceRule()
{
  this.type = kJscsspNAMESPACE_RULE;
  this.parsedCssText = null;
  this.prefix = null;
  this.url = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspNamespaceRule.prototype = {
  cssText: function() {
    return "@namespace " + (this.prefix ? this.prefix + " ": "")
                        + this.url
                        + ";";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (token.isAtRule("@namespace")) {
      if (parser.parseNamespaceRule(token, sheet)) {
        var newRule = sheet.cssRules[0];
        this.url = newRule.url;
        this.prefix = newRule.prefix;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspPAGE_RULE */

function jscsspPageRule()
{
  this.type = kJscsspPAGE_RULE;
  this.parsedCssText = null;
  this.pageSelector = null;
  this.declarations = [];
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspPageRule.prototype = {
  cssText: function() {
    var rv = gTABS + "@page "
                   + (this.pageSelector ? this.pageSelector + " ": "")
                   + "{\n";
    var preservedGTABS = gTABS;
    gTABS += "  ";
    for (var i = 0; i < this.declarations.length; i++)
      rv += gTABS + this.declarations[i].cssText() + "\n";
    gTABS = preservedGTABS;
    return rv + gTABS + "}";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (token.isAtRule("@page")) {
      if (parser.parsePageRule(token, sheet)) {
        var newRule = sheet.cssRules[0];
        this.pageSelector = newRule.pageSelector;
        this.declarations = newRule.declarations;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspSTYLE_RULE */

function jscsspStyleRule()
{
  this.type = kJscsspSTYLE_RULE;
  this.parsedCssText = null;
  this.declarations = []
  this.mSelectorText = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspStyleRule.prototype = {
  cssText: function() {
    var rv = gTABS + this.mSelectorText + " {";
    var preservedGTABS = gTABS;
    gTABS += "  ";
    for (var i = 0; i < this.declarations.length; i++) {
      var declText = this.declarations[i].cssText();
      if (declText)
        rv += gTABS + this.declarations[i].cssText() ;
    }
    gTABS = preservedGTABS;
    return rv + "\n" + gTABS + "}";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (!token.isNotNull()) {
      if (parser.parseStyleRule(token, sheet, false)) {
        var newRule = sheet.cssRules[0];
        this.mSelectorText = newRule.mSelectorText;
        this.declarations = newRule.declarations;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  },

  selectorText: function() {
    return this.mSelectorText;
  },

  setSelectorText: function(val) {
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (!token.isNotNull()) {
      var s = parser.parseSelector(token, true);
      if (s) {
        this.mSelectorText = s.selector;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

function jscsspStylesheet()
{
  this.cssRules = [];
  this.variables = {};
}

jscsspStylesheet.prototype = {
  insertRule: function(aRule, aIndex) {
    try {
     this.cssRules.splice(aIndex, 1, aRule);
    }
    catch(e) {
    }
  },

  deleteRule: function(aIndex) {
    try {
      this.cssRules.splice(aIndex);
    }
    catch(e) {
    }
  },

  cssText: function() {
    var rv = "";
    for (var i = 0; i < this.cssRules.length; i++)
      rv += this.cssRules[i].cssText() + "\n\n";
    return rv;
  }
};

var kJscsspINHERIT_VALUE = 0;
var kJscsspPRIMITIVE_VALUE = 1;
var kJscsspVARIABLE_VALUE = 4;

function jscsspVariable(aType, aSheet)
{
  this.value = "";
  this.type = aType;
  this.name  = null;
  this.parentRule = null;
  this.parentStyleSheet = aSheet;
}

jscsspVariable.prototype = {
  cssText: function() {
    if (this.type == kJscsspVARIABLE_VALUE)
      return this.resolveVariable(this.name, this.parentRule, this.parentStyleSheet);
    else
      return this.value;
  },

  setCssText: function(val) {
    if (this.type == kJscsspVARIABLE_VALUE)
      throw DOMException.SYNTAX_ERR;
    else
      this.value = val;
  },

  resolveVariable: function(aName, aRule, aSheet)
  {
    return "var(" + aName + ")";
  }
};

/* kJscsspVARIABLES_RULE */

function jscsspVariablesRule()
{
  this.type = kJscsspVARIABLES_RULE;
  this.parsedCssText = null;
  this.declarations = [];
  this.parentStyleSheet = null;
  this.parentRule = null;
  this.media = null;
}

jscsspVariablesRule.prototype = {
  cssText: function() {
    var rv = gTABS + "@variables " +
                     (this.media.length ? this.media.join(", ") + " " : "") +
                     "{\n";
    var preservedGTABS = gTABS;
    gTABS += "  ";
    for (var i = 0; i < this.declarations.length; i++)
      rv += gTABS + this.declarations[i].cssText() + "\n";
    gTABS = preservedGTABS;
    return rv + gTABS + "}";
  },

  setCssText: function(val) {
    var sheet = {cssRules: []};
    var parser = new CSSParser(val);
    var token = parser.getToken(true, true);
    if (token.isAtRule("@variables")) {
      if (parser.parseVariablesRule(token, sheet)) {
        var newRule = sheet.cssRules[0];
        this.declarations = newRule.declarations;
        this.parsedCssText = newRule.parsedCssText;
        return;
      }
    }
    throw DOMException.SYNTAX_ERR;
  }
};

/* kJscsspWHITE_SPACE */

function jscsspWhitespace()
{
  this.type = kJscsspWHITE_SPACE;
  this.parsedCssText = null;
  this.parentStyleSheet = null;
  this.parentRule = null;
}

jscsspWhitespace.prototype = {
  cssText: function() {
    return this.parsedCssText;
  }
};

var kJscsspUNKNOWN_RULE   = 0;
var kJscsspSTYLE_RULE     = 1
var kJscsspCHARSET_RULE   = 2;
var kJscsspIMPORT_RULE    = 3;
var kJscsspMEDIA_RULE     = 4;
var kJscsspFONT_FACE_RULE = 5;
var kJscsspPAGE_RULE      = 6;

var kJscsspKEYFRAMES_RULE = 7;
var kJscsspKEYFRAME_RULE  = 8;

var kJscsspNAMESPACE_RULE = 100;
var kJscsspCOMMENT        = 101;
var kJscsspWHITE_SPACE    = 102;

var kJscsspVARIABLES_RULE = 200;

var kJscsspSTYLE_DECLARATION = 1000;

var gTABS = "";

