Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | 21x 38x 38x 38x 27x 116x 116x 24x 24x 92x 92x 92x 92x 394x 394x 394x 394x 394x 394x 394x 394x 83x 394x 154x 154x 154x 394x 38x 18x 44x 44x 431x 431x 121x 121x 121x 83x 431x 431x 380x 380x 380x 311x 311x 38x 121x 24x 412x 1689x 21x 523x 523x 394x 394x 394x | /**
* Copyright (c) Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
*/
type Groups = { [key: number]: { start: ?number, rules: Array<string> } };
type Selectors = { [key: string]: boolean };
const slice = Array.prototype.slice;
/**
* Order-based insertion of CSS.
*
* Each rule is associated with a numerically defined group.
* Groups are ordered within the style sheet according to their number, with the
* lowest first.
*
* Groups are implemented using marker rules. The selector of the first rule of
* each group is used only to encode the group number for hydration. An
* alternative implementation could rely on CSSMediaRule, allowing groups to be
* treated as a sub-sheet, but the Edge implementation of CSSMediaRule is
* broken.
* https://developer.mozilla.org/en-US/docs/Web/API/CSSMediaRule
* https://gist.github.com/necolas/aa0c37846ad6bd3b05b727b959e82674
*/
export default function createOrderedCSSStyleSheet(
sheet: ?CSSStyleSheet
): {|
getTextContent: () => string,
insert: (cssText: string, groupValue: number) => void
|} {
const groups: Groups = {};
const selectors: Selectors = {};
/**
* Hydrate approximate record from any existing rules in the sheet.
*/
if (sheet != null) {
let group;
slice.call(sheet.cssRules).forEach((cssRule, i) => {
const cssText = cssRule.cssText;
// Create record of existing selectors and rules
if (cssText.indexOf('stylesheet-group') > -1) {
group = decodeGroupRule(cssRule);
groups[group] = { start: i, rules: [cssText] };
} else {
const selectorText = getSelectorText(cssText);
Eif (selectorText != null) {
selectors[selectorText] = true;
groups[group].rules.push(cssText);
}
}
});
}
function sheetInsert(sheet, group, text) {
const orderedGroups = getOrderedGroups(groups);
const groupIndex = orderedGroups.indexOf(group);
const nextGroupIndex = groupIndex + 1;
const nextGroup = orderedGroups[nextGroupIndex];
// Insert rule before the next group, or at the end of the stylesheet
const position =
nextGroup != null && groups[nextGroup].start != null
? groups[nextGroup].start
: sheet.cssRules.length;
const isInserted = insertRuleAt(sheet, text, position);
Eif (isInserted) {
// Set the starting index of the new group
if (groups[group].start == null) {
groups[group].start = position;
}
// Increment the starting index of all subsequent groups
for (let i = nextGroupIndex; i < orderedGroups.length; i += 1) {
const groupNumber = orderedGroups[i];
const previousStart = groups[groupNumber].start || 0;
groups[groupNumber].start = previousStart + 1;
}
}
return isInserted;
}
const OrderedCSSStyleSheet = {
/**
* The textContent of the style sheet.
*/
getTextContent(): string {
return getOrderedGroups(groups)
.map((group) => {
const rules = groups[group].rules;
return rules.join('\n');
})
.join('\n');
},
/**
* Insert a rule into the style sheet
*/
insert(cssText: string, groupValue: number) {
const group = Number(groupValue);
// Create a new group.
if (groups[group] == null) {
const markerRule = encodeGroupRule(group);
// Create the internal record.
groups[group] = { start: null, rules: [markerRule] };
// Update CSSOM.
if (sheet != null) {
sheetInsert(sheet, group, markerRule);
}
}
// selectorText is more reliable than cssText for insertion checks. The
// browser excludes vendor-prefixed properties and rewrites certain values
// making cssText more likely to be different from what was inserted.
const selectorText = getSelectorText(cssText);
if (selectorText != null && selectors[selectorText] == null) {
// Update the internal records.
selectors[selectorText] = true;
groups[group].rules.push(cssText);
// Update CSSOM.
if (sheet != null) {
const isInserted = sheetInsert(sheet, group, cssText);
Iif (!isInserted) {
// Revert internal record change if a rule was rejected (e.g.,
// unrecognized pseudo-selector)
groups[group].rules.pop();
}
}
}
}
};
return OrderedCSSStyleSheet;
}
/**
* Helper functions
*/
function encodeGroupRule(group) {
return `[stylesheet-group="${group}"]{}`;
}
function decodeGroupRule(cssRule) {
return Number(cssRule.selectorText.split(/["']/)[1]);
}
function getOrderedGroups(obj: { [key: number]: any }) {
return Object.keys(obj)
.map(Number)
.sort((a, b) => (a > b ? 1 : -1));
}
const pattern = /\s*([,])\s*/g;
function getSelectorText(cssText) {
const selector = cssText.split('{')[0].trim();
return selector !== '' ? selector.replace(pattern, '$1') : null;
}
function insertRuleAt(root, cssText: string, position: number): boolean {
try {
// $FlowFixMe: Flow is missing CSSOM types needed to type 'root'.
root.insertRule(cssText, position);
return true;
} catch (e) {
// JSDOM doesn't support `CSSSMediaRule#insertRule`.
// Also ignore errors that occur from attempting to insert vendor-prefixed selectors.
return false;
}
}
|