File: /home/alloesb/www/wp-content/plugins/customify/js/vendor/CSSOM.js
/**
* CSSOM v0.4.4
* https://github.com/NV/CSSOM
*
* @license MIT
* https://github.com/NV/CSSOM/blob/master/LICENSE.txt
*/
(function (window) {
'use strict'
const CSSOM = window.CSSOM = {}
/**
* @constructor
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
*/
CSSOM.CSSStyleDeclaration = function CSSStyleDeclaration () {
this.length = 0
this.parentRule = null
// NON-STANDARD
this._importants = {}
}
CSSOM.CSSStyleDeclaration.prototype = {
constructor: CSSOM.CSSStyleDeclaration,
/**
*
* @param {string} name
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue
* @return {string} the value of the property if it has been explicitly set for this declaration block.
* Returns the empty string if the property has not been set.
*/
getPropertyValue: function (name) {
return this[name] || ''
},
/**
*
* @param {string} name
* @param {string} value
* @param {string} [priority=null] "important" or null
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
*/
setProperty: function (name, value, priority) {
if (this[name]) {
// Property already exist. Overwrite it.
const index = Array.prototype.indexOf.call(this, name)
if (index < 0) {
this[this.length] = name
this.length++
}
} else {
// New property.
this[this.length] = name
this.length++
}
this[name] = value + ''
this._importants[name] = priority
},
/**
*
* @param {string} name
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty
* @return {string} the value of the property if it has been explicitly set for this declaration block.
* Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property.
*/
removeProperty: function (name) {
if (!(name in this)) {
return ''
}
const index = Array.prototype.indexOf.call(this, name)
if (index < 0) {
return ''
}
const prevValue = this[name]
this[name] = ''
// That's what WebKit and Opera do
Array.prototype.splice.call(this, index, 1)
// That's what Firefox does
//this[index] = ""
return prevValue
},
getPropertyCSSValue: function () {
//FIXME
},
/**
*
* @param {String} name
*/
getPropertyPriority: function (name) {
return this._importants[name] || ''
},
/**
* element.style.overflow = "auto"
* element.style.getPropertyShorthand("overflow-x")
* -> "overflow"
*/
getPropertyShorthand: function () {
//FIXME
},
isPropertyImplicit: function () {
//FIXME
},
// Doesn't work in IE < 9
get cssText () {
const properties = []
let i = 0, length = this.length
for (; i < length; ++i) {
const name = this[i]
const value = this.getPropertyValue(name)
let priority = this.getPropertyPriority(name)
if (priority) {
priority = ' !' + priority
}
properties[i] = name + ': ' + value + priority + ';'
}
return properties.join(' ')
},
set cssText (text) {
let i, name
for (i = this.length; i--;) {
name = this[i]
this[name] = ''
}
Array.prototype.splice.call(this, 0, this.length)
this._importants = {}
const dummyRule = CSSOM.parse('#bogus{' + text + '}').cssRules[0].style
const length = dummyRule.length
for (i = 0; i < length; ++i) {
name = dummyRule[i]
this.setProperty(dummyRule[i], dummyRule.getPropertyValue(name), dummyRule.getPropertyPriority(name))
}
}
}
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#the-cssrule-interface
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSRule
*/
CSSOM.CSSRule = function CSSRule () {
this.parentRule = null
this.parentStyleSheet = null
}
CSSOM.CSSRule.UNKNOWN_RULE = 0 // obsolete
CSSOM.CSSRule.STYLE_RULE = 1
CSSOM.CSSRule.CHARSET_RULE = 2 // obsolete
CSSOM.CSSRule.IMPORT_RULE = 3
CSSOM.CSSRule.MEDIA_RULE = 4
CSSOM.CSSRule.FONT_FACE_RULE = 5
CSSOM.CSSRule.PAGE_RULE = 6
CSSOM.CSSRule.KEYFRAMES_RULE = 7
CSSOM.CSSRule.KEYFRAME_RULE = 8
CSSOM.CSSRule.MARGIN_RULE = 9
CSSOM.CSSRule.NAMESPACE_RULE = 10
CSSOM.CSSRule.COUNTER_STYLE_RULE = 11
CSSOM.CSSRule.SUPPORTS_RULE = 12
CSSOM.CSSRule.DOCUMENT_RULE = 13
CSSOM.CSSRule.FONT_FEATURE_VALUES_RULE = 14
CSSOM.CSSRule.VIEWPORT_RULE = 15
CSSOM.CSSRule.REGION_STYLE_RULE = 16
CSSOM.CSSRule.prototype = {
constructor: CSSOM.CSSRule
//FIXME
}
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#cssstylerule
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule
*/
CSSOM.CSSStyleRule = function CSSStyleRule () {
CSSOM.CSSRule.call(this)
this.selectorText = ''
this.style = new CSSOM.CSSStyleDeclaration()
this.style.parentRule = this
}
CSSOM.CSSStyleRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule
CSSOM.CSSStyleRule.prototype.type = 1
Object.defineProperty(CSSOM.CSSStyleRule.prototype, 'cssText', {
get: function () {
let text
if (this.selectorText) {
text = this.selectorText + ' {' + this.style.cssText + '}'
} else {
text = ''
}
return text
},
set: function (cssText) {
const rule = CSSOM.CSSStyleRule.parse(cssText)
this.style = rule.style
this.selectorText = rule.selectorText
}
})
/**
* NON-STANDARD
* lightweight version of parse.js.
* @param {string} ruleText
* @return CSSStyleRule
*/
CSSOM.CSSStyleRule.parse = function (ruleText) {
let i = 0
let state = 'selector'
let index
let j = i
let buffer = ''
const SIGNIFICANT_WHITESPACE = {
'selector': true,
'value': true
}
const styleRule = new CSSOM.CSSStyleRule()
let name, priority = ''
for (let character; (character = ruleText.charAt(i)); i++) {
switch (character) {
case ' ':
case '\t':
case '\r':
case '\n':
case '\f':
if (SIGNIFICANT_WHITESPACE[state]) {
// Squash 2 or more white-spaces in the row into 1
switch (ruleText.charAt(i - 1)) {
case ' ':
case '\t':
case '\r':
case '\n':
case '\f':
break
default:
buffer += ' '
break
}
}
break
// String
case '"':
j = i + 1
index = ruleText.indexOf('"', j) + 1
if (!index) {
throw '" is missing'
}
buffer += ruleText.slice(i, index)
i = index - 1
break
case '\'':
j = i + 1
index = ruleText.indexOf('\'', j) + 1
if (!index) {
throw '\' is missing'
}
buffer += ruleText.slice(i, index)
i = index - 1
break
// Comment
case '/':
if (ruleText.charAt(i + 1) === '*') {
i += 2
index = ruleText.indexOf('*/', i)
if (index === -1) {
throw new SyntaxError('Missing */')
} else {
i = index + 1
}
} else {
buffer += character
}
break
case '{':
if (state === 'selector') {
styleRule.selectorText = buffer.trim()
buffer = ''
state = 'name'
}
break
case ':':
if (state === 'name') {
name = buffer.trim()
buffer = ''
state = 'value'
} else {
buffer += character
}
break
case '!':
if (state === 'value' && ruleText.indexOf('!important', i) === i) {
priority = 'important'
i += 'important'.length
} else {
buffer += character
}
break
case ';':
if (state === 'value') {
styleRule.style.setProperty(name, buffer.trim(), priority)
priority = ''
buffer = ''
state = 'name'
} else {
buffer += character
}
break
case '}':
if (state === 'value') {
styleRule.style.setProperty(name, buffer.trim(), priority)
priority = ''
buffer = ''
} else if (state === 'name') {
break
} else {
buffer += character
}
state = 'selector'
break
default:
buffer += character
break
}
}
return styleRule
}
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#the-medialist-interface
*/
CSSOM.MediaList = function MediaList () {
this.length = 0
}
CSSOM.MediaList.prototype = {
constructor: CSSOM.MediaList,
/**
* @return {string}
*/
get mediaText () {
return Array.prototype.join.call(this, ', ')
},
/**
* @param {string} value
*/
set mediaText (value) {
const values = value.split(',')
const length = this.length = values.length
for (let i = 0; i < length; i++) {
this[i] = values[i].trim()
}
},
/**
* @param {string} medium
*/
appendMedium: function (medium) {
if (Array.prototype.indexOf.call(this, medium) === -1) {
this[this.length] = medium
this.length++
}
},
/**
* @param {string} medium
*/
deleteMedium: function (medium) {
const index = Array.prototype.indexOf.call(this, medium)
if (index !== -1) {
Array.prototype.splice.call(this, index, 1)
}
}
}
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#cssmediarule
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSMediaRule
*/
CSSOM.CSSMediaRule = function CSSMediaRule () {
CSSOM.CSSRule.call(this)
this.media = new CSSOM.MediaList()
this.cssRules = []
}
CSSOM.CSSMediaRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule
CSSOM.CSSMediaRule.prototype.type = 4
//FIXME
//CSSOM.CSSMediaRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSMediaRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
// http://opensource.apple.com/source/WebCore/WebCore-658.28/css/CSSMediaRule.cpp
Object.defineProperty(CSSOM.CSSMediaRule.prototype, 'cssText', {
get: function () {
const cssTexts = []
for (var i = 0, length = this.cssRules.length; i < length; i++) {
cssTexts.push(this.cssRules[i].cssText)
}
return '@media ' + this.media.mediaText + ' {' + cssTexts.join('') + '}'
}
})
/**
* @constructor
* @see https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface
*/
CSSOM.CSSSupportsRule = function CSSSupportsRule () {
CSSOM.CSSRule.call(this)
this.conditionText = ''
this.cssRules = []
}
CSSOM.CSSSupportsRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSSupportsRule.prototype.constructor = CSSOM.CSSSupportsRule
CSSOM.CSSSupportsRule.prototype.type = 12
Object.defineProperty(CSSOM.CSSSupportsRule.prototype, 'cssText', {
get: function () {
const cssTexts = []
let i = 0, length = this.cssRules.length
for (; i < length; i++) {
cssTexts.push(this.cssRules[i].cssText)
}
return '@supports ' + this.conditionText + ' {' + cssTexts.join('') + '}'
}
})
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#cssimportrule
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSImportRule
*/
CSSOM.CSSImportRule = function CSSImportRule () {
CSSOM.CSSRule.call(this)
this.href = ''
this.media = new CSSOM.MediaList()
this.styleSheet = new CSSOM.CSSStyleSheet()
}
CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule
CSSOM.CSSImportRule.prototype.type = 3
Object.defineProperty(CSSOM.CSSImportRule.prototype, 'cssText', {
get: function () {
const mediaText = this.media.mediaText
return '@import url(' + this.href + ')' + (mediaText ? ' ' + mediaText : '') + ';'
},
set: function (cssText) {
let i = 0
/**
* @import url(partial.css) screen, handheld;
* || |
* after-import media
* |
* url
*/
let state = ''
let buffer = ''
let index
for (let character; (character = cssText.charAt(i)); i++) {
switch (character) {
case ' ':
case '\t':
case '\r':
case '\n':
case '\f':
if (state === 'after-import') {
state = 'url'
} else {
buffer += character
}
break
case '@':
if (!state && cssText.indexOf('@import', i) === i) {
state = 'after-import'
i += 'import'.length
buffer = ''
}
break
case 'u':
if (state === 'url' && cssText.indexOf('url(', i) === i) {
index = cssText.indexOf(')', i + 1)
if (index === -1) {
throw i + ': ")" not found'
}
i += 'url('.length
var url = cssText.slice(i, index)
if (url[0] === url[url.length - 1]) {
if (url[0] === '"' || url[0] === '\'') {
url = url.slice(1, -1)
}
}
this.href = url
i = index
state = 'media'
}
break
case '"':
if (state === 'url') {
index = cssText.indexOf('"', i + 1)
if (!index) {
throw i + ': \'"\' not found'
}
this.href = cssText.slice(i + 1, index)
i = index
state = 'media'
}
break
case '\'':
if (state === 'url') {
index = cssText.indexOf('\'', i + 1)
if (!index) {
throw i + ': "\'" not found'
}
this.href = cssText.slice(i + 1, index)
i = index
state = 'media'
}
break
case ';':
if (state === 'media') {
if (buffer) {
this.media.mediaText = buffer.trim()
}
}
break
default:
if (state === 'media') {
buffer += character
}
break
}
}
}
})
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#css-font-face-rule
*/
CSSOM.CSSFontFaceRule = function CSSFontFaceRule () {
CSSOM.CSSRule.call(this)
this.style = new CSSOM.CSSStyleDeclaration()
this.style.parentRule = this
}
CSSOM.CSSFontFaceRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule
CSSOM.CSSFontFaceRule.prototype.type = 5
//FIXME
//CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp
Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, 'cssText', {
get: function () {
return '@font-face {' + this.style.cssText + '}'
}
})
/**
* @constructor
* @see http://www.w3.org/TR/shadow-dom/#host-at-rule
*/
CSSOM.CSSHostRule = function CSSHostRule () {
CSSOM.CSSRule.call(this)
this.cssRules = []
}
CSSOM.CSSHostRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule
CSSOM.CSSHostRule.prototype.type = 1001
//FIXME
//CSSOM.CSSHostRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSHostRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
Object.defineProperty(CSSOM.CSSHostRule.prototype, 'cssText', {
get: function () {
const cssTexts = []
let i = 0, length = this.cssRules.length
for (; i < length; i++) {
cssTexts.push(this.cssRules[i].cssText)
}
return '@host {' + cssTexts.join('') + '}'
}
})
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
*/
CSSOM.StyleSheet = function StyleSheet () {
this.parentStyleSheet = null
}
/**
* @constructor
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
*/
CSSOM.CSSStyleSheet = function CSSStyleSheet () {
CSSOM.StyleSheet.call(this)
this.cssRules = []
}
CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet()
CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet
/**
* Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade.
*
* sheet = new Sheet("body {margin: 0}")
* sheet.toString()
* -> "body{margin:0;}"
* sheet.insertRule("img {border: none}", 0)
* -> 0
* sheet.toString()
* -> "img{border:none;}body{margin:0;}"
*
* @param {string} rule
* @param {number} index
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
* @return {number} The index within the style sheet's rule collection of the newly inserted rule.
*/
CSSOM.CSSStyleSheet.prototype.insertRule = function (rule, index) {
if (index < 0 || index > this.cssRules.length) {
throw new RangeError('INDEX_SIZE_ERR')
}
const cssRule = CSSOM.parse(rule).cssRules[0]
cssRule.parentStyleSheet = this
this.cssRules.splice(index, 0, cssRule)
return index
}
/**
* Used to delete a rule from the style sheet.
*
* sheet = new Sheet("img{border:none} body{margin:0}")
* sheet.toString()
* -> "img{border:none;}body{margin:0;}"
* sheet.deleteRule(0)
* sheet.toString()
* -> "body{margin:0;}"
*
* @param {number} index within the style sheet's rule list of the rule to remove.
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
*/
CSSOM.CSSStyleSheet.prototype.deleteRule = function (index) {
if (index < 0 || index >= this.cssRules.length) {
throw new RangeError('INDEX_SIZE_ERR')
}
this.cssRules.splice(index, 1)
}
/**
* NON-STANDARD
* @return {string} serialize stylesheet
*/
CSSOM.CSSStyleSheet.prototype.toString = function () {
let result = ''
const rules = this.cssRules
for (let i = 0; i < rules.length; i++) {
result += rules[i].cssText + '\n'
}
return result
}
/**
* @constructor
* @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframesRule
*/
CSSOM.CSSKeyframesRule = function CSSKeyframesRule () {
CSSOM.CSSRule.call(this)
this.name = ''
this.cssRules = []
}
CSSOM.CSSKeyframesRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule
CSSOM.CSSKeyframesRule.prototype.type = 7
//FIXME
//CSSOM.CSSKeyframesRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSKeyframesRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, 'cssText', {
get: function () {
const cssTexts = []
let i = 0, length = this.cssRules.length
for (; i < length; i++) {
cssTexts.push(' ' + this.cssRules[i].cssText)
}
return '@' + (this._vendorPrefix || '') + 'keyframes ' + this.name + ' { \n' + cssTexts.join('\n') + '\n}'
}
})
/**
* @constructor
* @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframeRule
*/
CSSOM.CSSKeyframeRule = function CSSKeyframeRule () {
CSSOM.CSSRule.call(this)
this.keyText = ''
this.style = new CSSOM.CSSStyleDeclaration()
this.style.parentRule = this
}
CSSOM.CSSKeyframeRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSKeyframeRule.prototype.constructor = CSSOM.CSSKeyframeRule
CSSOM.CSSKeyframeRule.prototype.type = 8
//FIXME
//CSSOM.CSSKeyframeRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSKeyframeRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframeRule.cpp
Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, 'cssText', {
get: function () {
return this.keyText + ' {' + this.style.cssText + '} '
}
})
/**
* @constructor
* @see https://developer.mozilla.org/en/CSS/@-moz-document
*/
CSSOM.MatcherList = function MatcherList () {
this.length = 0
}
CSSOM.MatcherList.prototype = {
constructor: CSSOM.MatcherList,
/**
* @return {string}
*/
get matcherText () {
return Array.prototype.join.call(this, ', ')
},
/**
* @param {string} value
*/
set matcherText (value) {
// just a temporary solution, actually it may be wrong by just split the value with ',', because a url can include ','.
const values = value.split(',')
const length = this.length = values.length
for (let i = 0; i < length; i++) {
this[i] = values[i].trim()
}
},
/**
* @param {string} matcher
*/
appendMatcher: function (matcher) {
if (Array.prototype.indexOf.call(this, matcher) === -1) {
this[this.length] = matcher
this.length++
}
},
/**
* @param {string} matcher
*/
deleteMatcher: function (matcher) {
const index = Array.prototype.indexOf.call(this, matcher)
if (index !== -1) {
Array.prototype.splice.call(this, index, 1)
}
}
}
/**
* @constructor
* @see https://developer.mozilla.org/en/CSS/@-moz-document
*/
CSSOM.CSSDocumentRule = function CSSDocumentRule () {
CSSOM.CSSRule.call(this)
this.matcher = new CSSOM.MatcherList()
this.cssRules = []
}
CSSOM.CSSDocumentRule.prototype = new CSSOM.CSSRule()
CSSOM.CSSDocumentRule.prototype.constructor = CSSOM.CSSDocumentRule
CSSOM.CSSDocumentRule.prototype.type = 10
//FIXME
//CSSOM.CSSDocumentRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSDocumentRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
Object.defineProperty(CSSOM.CSSDocumentRule.prototype, 'cssText', {
get: function () {
const cssTexts = []
let i = 0, length = this.cssRules.length
for (; i < length; i++) {
cssTexts.push(this.cssRules[i].cssText)
}
return '@-moz-document ' + this.matcher.matcherText + ' {' + cssTexts.join('') + '}'
}
})
/**
* @constructor
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
*
* TODO: add if needed
*/
CSSOM.CSSValue = function CSSValue () {
}
CSSOM.CSSValue.prototype = {
constructor: CSSOM.CSSValue,
// @see: http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
set cssText (text) {
const name = this._getConstructorName()
throw new Error('DOMException: property "cssText" of "' + name + '" is readonly and can not be replaced with "' + text + '"!')
},
get cssText () {
const name = this._getConstructorName()
throw new Error('getter "cssText" of "' + name + '" is not implemented!')
},
_getConstructorName: function () {
const s = this.constructor.toString(),
c = s.match(/function\s([^\(]+)/)
return c[1]
}
}
/**
* @constructor
* @see http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx
*
*/
CSSOM.CSSValueExpression = function CSSValueExpression (token, idx) {
this._token = token
this._idx = idx
}
CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue()
CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression
/**
* parse css expression() value
*
* @return {Object}
* - error:
* or
* - idx:
* - expression:
*
* Example:
*
* .selector {
* zoom: expression(documentElement.clientWidth > 1000 ? '1000px' : 'auto');
* }
*/
CSSOM.CSSValueExpression.prototype.parse = function () {
let token = this._token,
idx = this._idx
let character = '',
expression = '',
error = '',
info,
paren = []
for (; ; ++idx) {
character = token.charAt(idx)
// end of token
if (character === '') {
error = 'css expression error: unfinished expression!'
break
}
switch (character) {
case '(':
paren.push(character)
expression += character
break
case ')':
paren.pop(character)
expression += character
break
case '/':
if ((info = this._parseJSComment(token, idx))) { // comment?
if (info.error) {
error = 'css expression error: unfinished comment in expression!'
} else {
idx = info.idx
// ignore the comment
}
} else if ((info = this._parseJSRexExp(token, idx))) { // regexp
idx = info.idx
expression += info.text
} else { // other
expression += character
}
break
case '\'':
case '"':
info = this._parseJSString(token, idx, character)
if (info) { // string
idx = info.idx
expression += info.text
} else {
expression += character
}
break
default:
expression += character
break
}
if (error) {
break
}
// end of expression
if (paren.length === 0) {
break
}
}
let ret
if (error) {
ret = {
error: error
}
} else {
ret = {
idx: idx,
expression: expression
}
}
return ret
}
/**
*
* @return {Object|false}
* - idx:
* - text:
* or
* - error:
* or
* false
*
*/
CSSOM.CSSValueExpression.prototype._parseJSComment = function (token, idx) {
let nextChar = token.charAt(idx + 1),
text
if (nextChar === '/' || nextChar === '*') {
let startIdx = idx,
endIdx,
commentEndChar
if (nextChar === '/') { // line comment
commentEndChar = '\n'
} else if (nextChar === '*') { // block comment
commentEndChar = '*/'
}
endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1)
if (endIdx !== -1) {
endIdx = endIdx + commentEndChar.length - 1
text = token.substring(idx, endIdx + 1)
return {
idx: endIdx,
text: text
}
} else {
const error = 'css expression error: unfinished comment in expression!'
return {
error: error
}
}
} else {
return false
}
}
/**
*
* @return {Object|false}
* - idx:
* - text:
* or
* false
*
*/
CSSOM.CSSValueExpression.prototype._parseJSString = function (token, idx, sep) {
let endIdx = this._findMatchedIdx(token, idx, sep),
text
if (endIdx === -1) {
return false
} else {
text = token.substring(idx, endIdx + sep.length)
return {
idx: endIdx,
text: text
}
}
}
/**
* parse regexp in css expression
*
* @return {Object|false}
* - idx:
* - regExp:
* or
* false
*/
/*
all legal RegExp
/a/
(/a/)
[/a/]
[12, /a/]
!/a/
+/a/
-/a/
* /a/
/ /a/
%/a/
===/a/
!==/a/
==/a/
!=/a/
>/a/
>=/a/
</a/
<=/a/
&/a/
|/a/
^/a/
~/a/
<</a/
>>/a/
>>>/a/
&&/a/
||/a/
?/a/
=/a/
,/a/
delete /a/
in /a/
instanceof /a/
new /a/
typeof /a/
void /a/
*/
CSSOM.CSSValueExpression.prototype._parseJSRexExp = function (token, idx) {
const before = token.substring(0, idx).replace(/\s+$/, ''),
legalRegx = [
/^$/,
/\($/,
/\[$/,
/\!$/,
/\+$/,
/\-$/,
/\*$/,
/\/\s+/,
/\%$/,
/\=$/,
/\>$/,
/<$/,
/\&$/,
/\|$/,
/\^$/,
/\~$/,
/\?$/,
/\,$/,
/delete$/,
/in$/,
/instanceof$/,
/new$/,
/typeof$/,
/void$/
]
const isLegal = legalRegx.some(function (reg) {
return reg.test(before)
})
if (!isLegal) {
return false
} else {
const sep = '/'
// same logic as string
return this._parseJSString(token, idx, sep)
}
}
/**
*
* find next sep(same line) index in `token`
*
* @return {Number}
*
*/
CSSOM.CSSValueExpression.prototype._findMatchedIdx = function (token, idx, sep) {
let startIdx = idx,
endIdx
const NOT_FOUND = -1
while (true) {
endIdx = token.indexOf(sep, startIdx + 1)
if (endIdx === -1) { // not found
endIdx = NOT_FOUND
break
} else {
const text = token.substring(idx + 1, endIdx),
matched = text.match(/\\+$/)
if (!matched || matched[0] % 2 === 0) { // not escaped
break
} else {
startIdx = endIdx
}
}
}
// boundary must be in the same line(js sting or regexp)
const nextNewLineIdx = token.indexOf('\n', idx + 1)
if (nextNewLineIdx < endIdx) {
endIdx = NOT_FOUND
}
return endIdx
}
/**
* @param {string} token
*/
CSSOM.parse = function parse (token) {
let i = 0
/**
"before-selector" or
"selector" or
"atRule" or
"atBlock" or
"conditionBlock" or
"before-name" or
"name" or
"before-value" or
"value"
*/
let state = 'before-selector'
let index
let buffer = ''
let valueParenthesisDepth = 0
const SIGNIFICANT_WHITESPACE = {
'selector': true,
'value': true,
'value-parenthesis': true,
'atRule': true,
'importRule-begin': true,
'importRule': true,
'atBlock': true,
'conditionBlock': true,
'documentRule-begin': true
}
const styleSheet = new CSSOM.CSSStyleSheet()
// @type CSSStyleSheet|CSSMediaRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
let currentScope = styleSheet
// @type CSSMediaRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
let parentRule
const ancestorRules = []
let hasAncestors = false
let prevScope
let name, priority = '', styleRule, mediaRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule,
hostRule
const atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g
const parseError = function (message) {
const lines = token.substring(0, i).split('\n')
const lineCount = lines.length
const charCount = lines.pop().length + 1
const error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')')
error.line = lineCount
/* jshint sub : true */
error['char'] = charCount
error.styleSheet = styleSheet
throw error
}
for (let character; (character = token.charAt(i)); i++) {
switch (character) {
case ' ':
case '\t':
case '\r':
case '\n':
case '\f':
if (SIGNIFICANT_WHITESPACE[state]) {
buffer += character
}
break
// String
case '"':
index = i + 1
do {
index = token.indexOf('"', index) + 1
if (!index) {
parseError('Unmatched "')
}
} while (token[index - 2] === '\\')
buffer += token.slice(i, index)
i = index - 1
switch (state) {
case 'before-value':
state = 'value'
break
case 'importRule-begin':
state = 'importRule'
break
}
break
case '\'':
index = i + 1
do {
index = token.indexOf('\'', index) + 1
if (!index) {
parseError('Unmatched \'')
}
} while (token[index - 2] === '\\')
buffer += token.slice(i, index)
i = index - 1
switch (state) {
case 'before-value':
state = 'value'
break
case 'importRule-begin':
state = 'importRule'
break
}
break
// Comment
case '/':
if (token.charAt(i + 1) === '*') {
i += 2
index = token.indexOf('*/', i)
if (index === -1) {
parseError('Missing */')
} else {
i = index + 1
}
} else {
buffer += character
}
if (state === 'importRule-begin') {
buffer += ' '
state = 'importRule'
}
break
// At-rule
case '@':
if (token.indexOf('@-moz-document', i) === i) {
state = 'documentRule-begin'
documentRule = new CSSOM.CSSDocumentRule()
documentRule.__starts = i
i += '-moz-document'.length
buffer = ''
break
} else if (token.indexOf('@media', i) === i) {
state = 'atBlock'
mediaRule = new CSSOM.CSSMediaRule()
mediaRule.__starts = i
i += 'media'.length
buffer = ''
break
} else if (token.indexOf('@supports', i) === i) {
state = 'conditionBlock'
supportsRule = new CSSOM.CSSSupportsRule()
supportsRule.__starts = i
i += 'supports'.length
buffer = ''
break
} else if (token.indexOf('@host', i) === i) {
state = 'hostRule-begin'
i += 'host'.length
hostRule = new CSSOM.CSSHostRule()
hostRule.__starts = i
buffer = ''
break
} else if (token.indexOf('@import', i) === i) {
state = 'importRule-begin'
i += 'import'.length
buffer += '@import'
break
} else if (token.indexOf('@font-face', i) === i) {
state = 'fontFaceRule-begin'
i += 'font-face'.length
fontFaceRule = new CSSOM.CSSFontFaceRule()
fontFaceRule.__starts = i
buffer = ''
break
} else {
atKeyframesRegExp.lastIndex = i
const matchKeyframes = atKeyframesRegExp.exec(token)
if (matchKeyframes && matchKeyframes.index === i) {
state = 'keyframesRule-begin'
keyframesRule = new CSSOM.CSSKeyframesRule()
keyframesRule.__starts = i
keyframesRule._vendorPrefix = matchKeyframes[1] // Will come out as undefined if no prefix was found
i += matchKeyframes[0].length - 1
buffer = ''
break
} else if (state === 'selector') {
state = 'atRule'
}
}
buffer += character
break
case '{':
if (state === 'selector' || state === 'atRule') {
styleRule.selectorText = buffer.trim()
styleRule.style.__starts = i
buffer = ''
state = 'before-name'
} else if (state === 'atBlock') {
mediaRule.media.mediaText = buffer.trim()
if (parentRule) {
ancestorRules.push(parentRule)
}
currentScope = parentRule = mediaRule
mediaRule.parentStyleSheet = styleSheet
buffer = ''
state = 'before-selector'
} else if (state === 'conditionBlock') {
supportsRule.conditionText = buffer.trim()
if (parentRule) {
ancestorRules.push(parentRule)
}
currentScope = parentRule = supportsRule
supportsRule.parentStyleSheet = styleSheet
buffer = ''
state = 'before-selector'
} else if (state === 'hostRule-begin') {
if (parentRule) {
ancestorRules.push(parentRule)
}
currentScope = parentRule = hostRule
hostRule.parentStyleSheet = styleSheet
buffer = ''
state = 'before-selector'
} else if (state === 'fontFaceRule-begin') {
if (parentRule) {
fontFaceRule.parentRule = parentRule
}
fontFaceRule.parentStyleSheet = styleSheet
styleRule = fontFaceRule
buffer = ''
state = 'before-name'
} else if (state === 'keyframesRule-begin') {
keyframesRule.name = buffer.trim()
if (parentRule) {
ancestorRules.push(parentRule)
keyframesRule.parentRule = parentRule
}
keyframesRule.parentStyleSheet = styleSheet
currentScope = parentRule = keyframesRule
buffer = ''
state = 'keyframeRule-begin'
} else if (state === 'keyframeRule-begin') {
styleRule = new CSSOM.CSSKeyframeRule()
styleRule.keyText = buffer.trim()
styleRule.__starts = i
buffer = ''
state = 'before-name'
} else if (state === 'documentRule-begin') {
// FIXME: what if this '{' is in the url text of the match function?
documentRule.matcher.matcherText = buffer.trim()
if (parentRule) {
ancestorRules.push(parentRule)
documentRule.parentRule = parentRule
}
currentScope = parentRule = documentRule
documentRule.parentStyleSheet = styleSheet
buffer = ''
state = 'before-selector'
}
break
case ':':
if (state === 'name') {
name = buffer.trim()
buffer = ''
state = 'before-value'
} else {
buffer += character
}
break
case '(':
if (state === 'value') {
// ie css expression mode
if (buffer.trim() === 'expression') {
const info = (new CSSOM.CSSValueExpression(token, i)).parse()
if (info.error) {
parseError(info.error)
} else {
buffer += info.expression
i = info.idx
}
} else {
state = 'value-parenthesis'
//always ensure this is reset to 1 on transition
//from value to value-parenthesis
valueParenthesisDepth = 1
buffer += character
}
} else if (state === 'value-parenthesis') {
valueParenthesisDepth++
buffer += character
} else {
buffer += character
}
break
case ')':
if (state === 'value-parenthesis') {
valueParenthesisDepth--
if (valueParenthesisDepth === 0) state = 'value'
}
buffer += character
break
case '!':
if (state === 'value' && token.indexOf('!important', i) === i) {
priority = 'important'
i += 'important'.length
} else {
buffer += character
}
break
case ';':
switch (state) {
case 'value':
styleRule.style.setProperty(name, buffer.trim(), priority)
priority = ''
buffer = ''
state = 'before-name'
break
case 'atRule':
buffer = ''
state = 'before-selector'
break
case 'importRule':
importRule = new CSSOM.CSSImportRule()
importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet
importRule.cssText = buffer + character
styleSheet.cssRules.push(importRule)
buffer = ''
state = 'before-selector'
break
default:
buffer += character
break
}
break
case '}':
switch (state) {
case 'value':
styleRule.style.setProperty(name, buffer.trim(), priority)
priority = ''
/* falls through */
case 'before-name':
case 'name':
styleRule.__ends = i + 1
if (parentRule) {
styleRule.parentRule = parentRule
}
styleRule.parentStyleSheet = styleSheet
currentScope.cssRules.push(styleRule)
buffer = ''
if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
state = 'keyframeRule-begin'
} else {
state = 'before-selector'
}
break
case 'keyframeRule-begin':
case 'before-selector':
case 'selector':
// End of media/supports/document rule.
if (!parentRule) {
parseError('Unexpected }')
}
// Handle rules nested in @media or @supports
hasAncestors = ancestorRules.length > 0
while (ancestorRules.length > 0) {
parentRule = ancestorRules.pop()
if (
parentRule.constructor.name === 'CSSMediaRule'
|| parentRule.constructor.name === 'CSSSupportsRule'
) {
prevScope = currentScope
currentScope = parentRule
currentScope.cssRules.push(prevScope)
break
}
if (ancestorRules.length === 0) {
hasAncestors = false
}
}
if (!hasAncestors) {
currentScope.__ends = i + 1
styleSheet.cssRules.push(currentScope)
currentScope = styleSheet
parentRule = null
}
buffer = ''
state = 'before-selector'
break
}
break
default:
switch (state) {
case 'before-selector':
state = 'selector'
styleRule = new CSSOM.CSSStyleRule()
styleRule.__starts = i
break
case 'before-name':
state = 'name'
break
case 'before-value':
state = 'value'
break
case 'importRule-begin':
state = 'importRule'
break
}
buffer += character
break
}
}
return styleSheet
}
/**
* Produces a deep copy of stylesheet — the instance variables of stylesheet are copied recursively.
* @param {CSSStyleSheet|CSSOM.CSSStyleSheet} stylesheet
* @nosideeffects
* @return {CSSOM.CSSStyleSheet}
*/
CSSOM.clone = function clone (stylesheet) {
const cloned = new CSSOM.CSSStyleSheet()
const rules = stylesheet.cssRules
if (!rules) {
return cloned
}
let i = 0, rulesLength = rules.length
for (; i < rulesLength; i++) {
const rule = rules[i]
const ruleClone = cloned.cssRules[i] = new rule.constructor()
const style = rule.style
if (style) {
const styleClone = ruleClone.style = new CSSOM.CSSStyleDeclaration()
let j = 0, styleLength = style.length
for (; j < styleLength; j++) {
const name = styleClone[j] = style[j]
styleClone[name] = style[name]
styleClone._importants[name] = style.getPropertyPriority(name)
}
styleClone.length = style.length
}
if (rule.hasOwnProperty('keyText')) {
ruleClone.keyText = rule.keyText
}
if (rule.hasOwnProperty('selectorText')) {
ruleClone.selectorText = rule.selectorText
}
if (rule.hasOwnProperty('mediaText')) {
ruleClone.mediaText = rule.mediaText
}
if (rule.hasOwnProperty('conditionText')) {
ruleClone.conditionText = rule.conditionText
}
if (rule.hasOwnProperty('cssRules')) {
ruleClone.cssRules = clone(rule).cssRules
}
}
return cloned
}
})(window)