blob: 691849b0b1d9608ddda2a816bac8129c81185b19 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
camelizeKeys,
// hyphenateKeys,
extend,
extendTruthy,
trimComment,
normalizeStyle,
autoPrefix,
isArray,
getParentScroller,
supportSticky,
appendCss
} from '../utils'
import { tagBegin, tagEnd } from '../utils/perf'
/* istanbul ignore next */
let pseudoId = 0
/**
* get scoped class style map from stylesheets in <head>.
*/
export function getHeadStyleMap () {
if (process.env.NODE_ENV === 'development') {
tagBegin('getHeadStyleMap')
}
const needToRemoveStyleSheetNodes = []
const styleSheetsArr = Array.from(document.styleSheets || []).filter(function (styleSheet) {
return styleSheet.ownerNode.getAttribute('weex-scanned') !== '1'
})
const res = Array.from(styleSheetsArr)
.reduce((pre, styleSheet) => {
styleSheet.ownerNode.setAttribute('weex-scanned', 1)
/**
* why not using styleSheet.rules || styleSheet.cssRules to get css rules ?
* because weex's components defined non-standard style attributes, which is
* auto ignored when access rule.cssText.
* another reason not to use cssRules directy:
* @issue: https://stackoverflow.com/questions/21642277/security-error-the-operation-is-insecure-in-firefox-document-stylesheets
*/
if (
// css in a link. just ignore this. probably a link stylesheet.
(styleSheet.ownerNode.tagName.toLowerCase() === 'link')
|| !styleSheet.ownerNode.textContent
// pseudo class styleSheet node is generated by weex. just ignore it.
|| styleSheet.ownerNode.id.match(/weex-pseudo-\d+/)) {
return pre
}
/**
* start to analyze it's content.
*/
const strArr = trimComment(styleSheet.ownerNode.textContent.trim()).split(/}/)
const len = strArr.length
const rules = []
for (let i = 0; i < len; i++) {
const str = strArr[i]
if (!str || str.match(/^\s*$/)) {
continue
}
/**
* should match these cases:
* .a[data-v-xxx] { color: red; }
* .a[data-v-xxx]:active { color: green; }
* .a[data-v-xxx], .b[data-v-xxx] { color: red; }
*
* should not match these cases:
* .a { color: red; }
* etc.
*/
const match = str.match(/((?:,?\s*\.[\w-]+\[data-v-\w+\](?::\w+)?)+)\s*({[^}]+)/)
if (!match) {
// not the vue static class styles map. so acquire no rules for this styleSheet.
// just jump through this styleSheet and go to analyzing next.
return pre
}
const clsNms = match[1].split(',').map(n => n.trim())
const cssText = match[2].replace(/[{}]/g, '').trim()
let clsNmsIdx = 0
const clsNmsLen = clsNms.length
while (clsNmsIdx < clsNmsLen) {
rules.push({
selectorText: clsNms[clsNmsIdx],
cssText
})
clsNmsIdx++
}
}
Array.from(rules).forEach(rule => {
const selector = rule.selectorText || ''
let isPseudo = false
if (selector.match(/:(?:active|focus|enabled|disabled)/)) {
isPseudo = true
}
const styleObj = trimComment(rule.cssText).split(';')
.reduce((styleObj, statement) => {
statement = statement.trim()
if (statement && statement.indexOf('/*') <= -1) {
const resArr = statement.split(':').map((part) => part.trim())
styleObj[resArr[0]] = resArr[1]
}
return styleObj
}, {})
if (isPseudo) {
const txt = Object.keys(styleObj).reduce(function (pre, cur) {
return pre + `${cur}:${styleObj[cur]}!important;`
}, '')
appendCss(`${selector}{${txt}}`, `weex-pseudo-${pseudoId++}`)
}
const objMap = !isPseudo ? pre : pre.pseudo
const res = objMap[selector]
if (!res) {
objMap[selector] = styleObj
}
else {
extend(objMap[selector], styleObj)
}
})
/**
* remove this styleSheet node since it's in the styleMap already. And this style
* should only be fetched and used from styleMap to generate the final combined
* component style, not from the stylesheet itself.
*/
needToRemoveStyleSheetNodes.push(styleSheet.ownerNode)
return pre
}, { pseudo: {}})
if (!window._no_remove_style_sheets) {
needToRemoveStyleSheetNodes.forEach(function (node) {
node.parentNode.removeChild(node)
})
}
else if (process.env.NODE_ENV === 'development') {
console.warn(`[vue-render] you've defined '_no_remove_style_sheets' and the v-data-xx stylesheets will not be removed.`)
}
if (process.env.NODE_ENV === 'development') {
tagEnd('getHeadStyleMap')
}
return res
}
// export function getScopeIds (context) {
// const arr = []
// let ctx = context
// let scopeId
// while (ctx) {
// scopeId = ctx.$options._scopeId
// scopeId && arr.push(scopeId)
// ctx = ctx.$options.parent
// }
// return arr
// }
export function getScopeId (vnode) {
return vnode.context.$options._scopeId
}
/**
* get style in <style scoped> tags for this component.
*/
export function getScopeStyle (vnode, classNames) {
const scopeId = getScopeId(vnode)
const style = {}
const styleMap = weex._styleMap || {}
let clsNmsIdx = 0
const clsNmsLen = classNames.length
while (clsNmsIdx < clsNmsLen) {
const cls = `.${classNames[clsNmsIdx]}[${scopeId}]`
const map = styleMap[cls]
map && extendTruthy(style, map)
clsNmsIdx++
}
return camelizeKeys(style)
}
function getStyle (vnode, extract) {
const data = vnode.data || {}
const staticClassNames = (typeof data.staticClass === 'string') ? data.staticClass.split(' ') : (data.staticClass || [])
const classNames = (typeof data.class === 'string') ? data.class.split(' ') : (data.class || [])
const clsNms = staticClassNames.concat(classNames)
const style = normalizeStyle(getScopeStyle(vnode, clsNms))
/**
* cache static style and bind style.
* cached staticStyle (including style and staticStyle) has already been normalized
* in $processStyle. So there's no need to normalize it again.
*/
if (!data.cached) {
// cache staticStyle once in the beginning.
data.cached = extendTruthy({}, data.staticStyle)
}
// cache binding style every time since the binding style is variable.
extendTruthy(data.cached, data.style)
extend(style, data.cached)
data.staticStyle = style
if (extract) {
delete data.staticStyle
delete data.style
}
return style
}
/**
* get style merged with static styles, binding styles, and scoped class styles,
* with keys in camelcase.
*/
export function getComponentStyle (context, extract) {
if (!context.$vnode) {
if (process.env.NODE_ENV === 'development') {
return console.error('[vue-render] getComponentStyle failed: no $vnode in context.')
}
return {}
}
let style = {}
let vnode = context.$vnode
while (vnode) {
extend(style, getStyle(vnode, extract))
vnode = vnode.parent
}
style = autoPrefix(style)
/**
* when prefixed value is a array, it should be applied to element
* during the next tick.
* e.g.
* background-image: linear-gradient(to top,#f5fefd,#ffffff);
* will generate:
* {
* backgroundImage: [
* "-webkit-linear-gradient(to top,#f5fefd,#ffffff)",
* "-moz-linear-gradient(to top,#f5fefd,#ffffff)",
* "linear-gradient(to top,#f5fefd,#ffffff)"]
* }
*/
for (const k in style) {
if (Array.isArray(style[k])) {
const vals = style[k]
context.$nextTick(function () {
const el = context.$el
if (el) {
for (let i = 0; i < vals.length; i++) {
el.style[k] = vals[i]
}
}
})
if (k !== 'position') { delete style[k] }
}
}
/**
* If position is 'sticky', then add it to the stickyChildren of the parent scroller.
*/
const pos = style.position
const reg = /sticky$/
if (pos === 'fixed') {
context.$nextTick(function () {
const el = context.$el
if (el) {
el.classList.add('weex-fixed')
}
})
}
else if (isArray(pos) && pos[0].match(reg) || (pos + '').match(reg)) {
delete style.position
// use native sticky.
if (supportSticky()) {
context.$nextTick(function () {
const el = context.$el
if (el) {
el.classList.add('weex-ios-sticky')
}
})
}
// use re-implementation of sticky.
else if (!context._stickyAdded) {
const uid = context._uid
const scroller = getParentScroller(context)
if (scroller) {
context._stickyAdded = true
if (!scroller._stickyChildren) {
scroller._stickyChildren = {}
}
scroller._stickyChildren[uid] = context
}
context.$nextTick(function () {
const el = context.$el
if (el) {
context._initOffsetTop = el.offsetTop
}
})
}
}
return style
}
export function extractComponentStyle (context) {
return getComponentStyle(context, true)
}