* rrdom: add a diff function for properties * implement diffChildren function and unit tests * finish basic functions of diff algorithm * fix several bugs in the diff algorithm * replace the virtual parent optimization in applyMutation() * fix: moveAndHover after the diff algorithm is executed * replace virtual style map with rrdom cssom version has to be above 0.5.0 to pass virtual style tests * fix: failed virtual style tests in replayer.test.ts * fix: failed polyfill tests caused by nodejs compatibility of different versions * fix: svg viewBox attribute doesn't work Cause the attribute viewBox is case sensitive, set value for viewbox doesn't work * feat: replace treeIndex optimization with rrdom * fix bug of diffProps and disable smooth scrolling animation in fast-forward mode * feat: add iframe support * fix: @rollup/plugin-typescript build errors in rrweb-player Error: @rollup/plugin-typescript TS1371: This import is never used as a value and must use 'import type' because the 'importsNotUsedAsValues' is set to 'error' * fix: bug when fast-forward input events and add test for it * add test for fast-forward scroll events * fix: custom style rules don't get inserted into some iframe elements * code style tweak * fix: enable to diff iframe elements * fix the jest error "Unexpected token 'export'" * try to fix build error of rrweb-player * correct the attributes definition in rrdom * fix: custom style rules are not inserted in some iframes * add support for shadow dom * add support for MediaInteraction * add canvas support * fix unit test error in rrdom * add support for Text, Comment * try to refactor RRDom * refactor RRDom to reduce duplicate code * rename document-browser to virtual-dom * increase the test coverage for document.ts and add ownerDocument for it * Merge branch 'master' into virtual-dom * add more test for virtual-dom.ts * use cssstyle in document-nodejs * fix: bundle error * improve document-nodejs * enable to diff scroll positions of an element * rename rrdom to virtualDom for more readability and make the tree public * revert unknown change * improve the css style parser for comments * improve code style * update typings * add handling for the case where legacy_missingNodeRetryMap is not empty * only import types from rrweb into rrdom * Apply suggestions from code review Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com> * Apply suggestions from code review * fix building error in rrweb * add a method setDefaultSN to set a default value for a RRNode's __sn * fix rrweb test error and bump up other packages * add support for custom property of css styles * add a switch for virtual-dom optimization * Apply suggestions from code review 1. add an enum type for NodeType 2. rename nodeType from rrweb-snapshot to RRNodeType 3. rename notSerializedId to unserializedId 4. add comments for some confusing variables * adapt changes of #865 to virtual-dom and improve the test case for more coverage * apply review suggestions https://github.com/rrweb-io/rrweb/pull/853#pullrequestreview-922474953 * tweak the diff algorithm * add description of the flag useVirtualDom and remove outdated logConfig * Remove console.log * Contain changes to document * Upgrade rollup to 2.70.2 * Revert "Upgrade rollup to 2.70.2" This reverts commit b1be81a2a76565935c9dc391f31beb7f64d25956. * Fix type checking rrdom * Fix typing error while bundling * Fix tslib error on build Rollup would output the following error: `semantic error TS2343: This syntax requires an imported helper named '__spreadArray' which does not exist in 'tslib'. Consider upgrading your version of 'tslib'.` * Increase memory limit for rollup * Use esbuild for bundling Speeds up bundling significantly * Avoid circular dependencies and import un-bundled rrdom * Fix imports * Revert back to pre-esbuild This reverts the following commits: b7b3c8dbaa551a0129da1477136b1baaad28e6e1 72e23b8e27f9030d911358d3a17fe5ad1b3b5d4f 85d600a20c56cfa764cf1f858932ba14e67b1d23 61e1a5d323212ca8fbe0569e0b3062ddd53fc612 * Set node to lts (12 is no longer supported) * Speed up bundling and use less memory This fixes the out of memory errors happening while bundling * remove __sn from rrdom * fix typo * test: add a test case for StyleSheet mutation exceptions while fast-forwarding * rename Array.prototype.slice.call() to Array.from() * improve test cases * fix: PR #887 in 'virtual-dom' branch * apply justin's suggestion on 'Array.from' refactor related commit 0f6729d27a323260b36fbe79485a86715c0bc98a * improve import code structure Co-authored-by: Yun Feng <yun.feng@anu.edu.au>
This commit is contained in:
513
packages/rrdom/src/diff.ts
Normal file
513
packages/rrdom/src/diff.ts
Normal file
@@ -0,0 +1,513 @@
|
||||
import { NodeType as RRNodeType, Mirror as NodeMirror } from 'rrweb-snapshot';
|
||||
import type {
|
||||
canvasMutationData,
|
||||
canvasEventWithTime,
|
||||
inputData,
|
||||
scrollData,
|
||||
} from 'rrweb/src/types';
|
||||
import type {
|
||||
IRRCDATASection,
|
||||
IRRComment,
|
||||
IRRDocument,
|
||||
IRRDocumentType,
|
||||
IRRElement,
|
||||
IRRNode,
|
||||
IRRText,
|
||||
} from './document';
|
||||
import type {
|
||||
RRCanvasElement,
|
||||
RRElement,
|
||||
RRIFrameElement,
|
||||
RRMediaElement,
|
||||
RRStyleElement,
|
||||
RRDocument,
|
||||
Mirror,
|
||||
} from './virtual-dom';
|
||||
|
||||
const NAMESPACES: Record<string, string> = {
|
||||
svg: 'http://www.w3.org/2000/svg',
|
||||
'xlink:href': 'http://www.w3.org/1999/xlink',
|
||||
xmlns: 'http://www.w3.org/2000/xmlns/',
|
||||
};
|
||||
|
||||
// camel case svg element tag names
|
||||
const SVGTagMap: Record<string, string> = {
|
||||
altglyph: 'altGlyph',
|
||||
altglyphdef: 'altGlyphDef',
|
||||
altglyphitem: 'altGlyphItem',
|
||||
animatecolor: 'animateColor',
|
||||
animatemotion: 'animateMotion',
|
||||
animatetransform: 'animateTransform',
|
||||
clippath: 'clipPath',
|
||||
feblend: 'feBlend',
|
||||
fecolormatrix: 'feColorMatrix',
|
||||
fecomponenttransfer: 'feComponentTransfer',
|
||||
fecomposite: 'feComposite',
|
||||
feconvolvematrix: 'feConvolveMatrix',
|
||||
fediffuselighting: 'feDiffuseLighting',
|
||||
fedisplacementmap: 'feDisplacementMap',
|
||||
fedistantlight: 'feDistantLight',
|
||||
fedropshadow: 'feDropShadow',
|
||||
feflood: 'feFlood',
|
||||
fefunca: 'feFuncA',
|
||||
fefuncb: 'feFuncB',
|
||||
fefuncg: 'feFuncG',
|
||||
fefuncr: 'feFuncR',
|
||||
fegaussianblur: 'feGaussianBlur',
|
||||
feimage: 'feImage',
|
||||
femerge: 'feMerge',
|
||||
femergenode: 'feMergeNode',
|
||||
femorphology: 'feMorphology',
|
||||
feoffset: 'feOffset',
|
||||
fepointlight: 'fePointLight',
|
||||
fespecularlighting: 'feSpecularLighting',
|
||||
fespotlight: 'feSpotLight',
|
||||
fetile: 'feTile',
|
||||
feturbulence: 'feTurbulence',
|
||||
foreignobject: 'foreignObject',
|
||||
glyphref: 'glyphRef',
|
||||
lineargradient: 'linearGradient',
|
||||
radialgradient: 'radialGradient',
|
||||
};
|
||||
|
||||
export type ReplayerHandler = {
|
||||
mirror: NodeMirror;
|
||||
applyCanvas: (
|
||||
canvasEvent: canvasEventWithTime,
|
||||
canvasMutationData: canvasMutationData,
|
||||
target: HTMLCanvasElement,
|
||||
) => void;
|
||||
applyInput: (data: inputData) => void;
|
||||
applyScroll: (data: scrollData, isSync: boolean) => void;
|
||||
};
|
||||
|
||||
export function diff(
|
||||
oldTree: Node,
|
||||
newTree: IRRNode,
|
||||
replayer: ReplayerHandler,
|
||||
rrnodeMirror?: Mirror,
|
||||
) {
|
||||
const oldChildren = oldTree.childNodes;
|
||||
const newChildren = newTree.childNodes;
|
||||
rrnodeMirror =
|
||||
rrnodeMirror ||
|
||||
(newTree as RRDocument).mirror ||
|
||||
(newTree.ownerDocument as RRDocument).mirror;
|
||||
|
||||
if (oldChildren.length > 0 || newChildren.length > 0) {
|
||||
diffChildren(
|
||||
Array.from(oldChildren),
|
||||
newChildren,
|
||||
oldTree,
|
||||
replayer,
|
||||
rrnodeMirror,
|
||||
);
|
||||
}
|
||||
|
||||
let inputDataToApply = null,
|
||||
scrollDataToApply = null;
|
||||
switch (newTree.RRNodeType) {
|
||||
case RRNodeType.Document:
|
||||
const newRRDocument = newTree as IRRDocument;
|
||||
scrollDataToApply = (newRRDocument as RRDocument).scrollData;
|
||||
break;
|
||||
case RRNodeType.Element:
|
||||
const oldElement = (oldTree as Node) as HTMLElement;
|
||||
const newRRElement = newTree as IRRElement;
|
||||
diffProps(oldElement, newRRElement, rrnodeMirror);
|
||||
scrollDataToApply = (newRRElement as RRElement).scrollData;
|
||||
inputDataToApply = (newRRElement as RRElement).inputData;
|
||||
switch (newRRElement.tagName) {
|
||||
case 'AUDIO':
|
||||
case 'VIDEO':
|
||||
const oldMediaElement = (oldTree as Node) as HTMLMediaElement;
|
||||
const newMediaRRElement = newRRElement as RRMediaElement;
|
||||
if (newMediaRRElement.paused !== undefined)
|
||||
newMediaRRElement.paused
|
||||
? oldMediaElement.pause()
|
||||
: oldMediaElement.play();
|
||||
if (newMediaRRElement.muted !== undefined)
|
||||
oldMediaElement.muted = newMediaRRElement.muted;
|
||||
if (newMediaRRElement.volume !== undefined)
|
||||
oldMediaElement.volume = newMediaRRElement.volume;
|
||||
if (newMediaRRElement.currentTime !== undefined)
|
||||
oldMediaElement.currentTime = newMediaRRElement.currentTime;
|
||||
break;
|
||||
case 'CANVAS':
|
||||
(newTree as RRCanvasElement).canvasMutations.forEach(
|
||||
(canvasMutation) =>
|
||||
replayer.applyCanvas(
|
||||
canvasMutation.event,
|
||||
canvasMutation.mutation,
|
||||
(oldTree as Node) as HTMLCanvasElement,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case 'STYLE':
|
||||
applyVirtualStyleRulesToNode(
|
||||
oldElement as HTMLStyleElement,
|
||||
(newTree as RRStyleElement).rules,
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (newRRElement.shadowRoot) {
|
||||
if (!oldElement.shadowRoot) oldElement.attachShadow({ mode: 'open' });
|
||||
const oldChildren = oldElement.shadowRoot!.childNodes;
|
||||
const newChildren = newRRElement.shadowRoot.childNodes;
|
||||
if (oldChildren.length > 0 || newChildren.length > 0)
|
||||
diffChildren(
|
||||
Array.from(oldChildren),
|
||||
newChildren,
|
||||
oldElement.shadowRoot!,
|
||||
replayer,
|
||||
rrnodeMirror,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case RRNodeType.Text:
|
||||
case RRNodeType.Comment:
|
||||
case RRNodeType.CDATA:
|
||||
if (
|
||||
oldTree.textContent !==
|
||||
(newTree as IRRText | IRRComment | IRRCDATASection).data
|
||||
)
|
||||
oldTree.textContent = (newTree as
|
||||
| IRRText
|
||||
| IRRComment
|
||||
| IRRCDATASection).data;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
scrollDataToApply && replayer.applyScroll(scrollDataToApply, true);
|
||||
/**
|
||||
* Input data need to get applied after all children of this node are updated.
|
||||
* Otherwise when we set a value for a select element whose options are empty, the value won't actually update.
|
||||
*/
|
||||
inputDataToApply && replayer.applyInput(inputDataToApply);
|
||||
|
||||
// IFrame element doesn't have child nodes.
|
||||
if (newTree.nodeName === 'IFRAME') {
|
||||
const oldContentDocument = ((oldTree as Node) as HTMLIFrameElement)
|
||||
.contentDocument;
|
||||
const newIFrameElement = newTree as RRIFrameElement;
|
||||
// If the iframe is cross-origin, the contentDocument will be null.
|
||||
if (oldContentDocument) {
|
||||
const sn = rrnodeMirror.getMeta(newIFrameElement.contentDocument);
|
||||
if (sn) {
|
||||
replayer.mirror.add(oldContentDocument, { ...sn });
|
||||
}
|
||||
diff(
|
||||
oldContentDocument,
|
||||
newIFrameElement.contentDocument,
|
||||
replayer,
|
||||
rrnodeMirror,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function diffProps(
|
||||
oldTree: HTMLElement,
|
||||
newTree: IRRElement,
|
||||
rrnodeMirror: Mirror,
|
||||
) {
|
||||
const oldAttributes = oldTree.attributes;
|
||||
const newAttributes = newTree.attributes;
|
||||
|
||||
for (const name in newAttributes) {
|
||||
const newValue = newAttributes[name];
|
||||
const sn = rrnodeMirror.getMeta(newTree);
|
||||
if (sn && 'isSVG' in sn && sn.isSVG && NAMESPACES[name])
|
||||
oldTree.setAttributeNS(NAMESPACES[name], name, newValue);
|
||||
else if (newTree.tagName === 'CANVAS' && name === 'rr_dataURL') {
|
||||
const image = document.createElement('img');
|
||||
image.src = newValue;
|
||||
image.onload = () => {
|
||||
const ctx = (oldTree as HTMLCanvasElement).getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.drawImage(image, 0, 0, image.width, image.height);
|
||||
}
|
||||
};
|
||||
} else oldTree.setAttribute(name, newValue);
|
||||
}
|
||||
|
||||
for (const { name } of Array.from(oldAttributes))
|
||||
if (!(name in newAttributes)) oldTree.removeAttribute(name);
|
||||
|
||||
newTree.scrollLeft && (oldTree.scrollLeft = newTree.scrollLeft);
|
||||
newTree.scrollTop && (oldTree.scrollTop = newTree.scrollTop);
|
||||
}
|
||||
|
||||
function diffChildren(
|
||||
oldChildren: (Node | undefined)[],
|
||||
newChildren: IRRNode[],
|
||||
parentNode: Node,
|
||||
replayer: ReplayerHandler,
|
||||
rrnodeMirror: Mirror,
|
||||
) {
|
||||
let oldStartIndex = 0,
|
||||
oldEndIndex = oldChildren.length - 1,
|
||||
newStartIndex = 0,
|
||||
newEndIndex = newChildren.length - 1;
|
||||
let oldStartNode = oldChildren[oldStartIndex],
|
||||
oldEndNode = oldChildren[oldEndIndex],
|
||||
newStartNode = newChildren[newStartIndex],
|
||||
newEndNode = newChildren[newEndIndex];
|
||||
let oldIdToIndex: Record<number, number> | undefined = undefined,
|
||||
indexInOld;
|
||||
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
|
||||
if (oldStartNode === undefined) {
|
||||
oldStartNode = oldChildren[++oldStartIndex];
|
||||
} else if (oldEndNode === undefined) {
|
||||
oldEndNode = oldChildren[--oldEndIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldStartNode) === rrnodeMirror.getId(newStartNode)
|
||||
) {
|
||||
diff(oldStartNode, newStartNode, replayer, rrnodeMirror);
|
||||
oldStartNode = oldChildren[++oldStartIndex];
|
||||
newStartNode = newChildren[++newStartIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldEndNode) === rrnodeMirror.getId(newEndNode)
|
||||
) {
|
||||
diff(oldEndNode, newEndNode, replayer, rrnodeMirror);
|
||||
oldEndNode = oldChildren[--oldEndIndex];
|
||||
newEndNode = newChildren[--newEndIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldStartNode) === rrnodeMirror.getId(newEndNode)
|
||||
) {
|
||||
parentNode.insertBefore(oldStartNode, oldEndNode.nextSibling);
|
||||
diff(oldStartNode, newEndNode, replayer, rrnodeMirror);
|
||||
oldStartNode = oldChildren[++oldStartIndex];
|
||||
newEndNode = newChildren[--newEndIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldEndNode) === rrnodeMirror.getId(newStartNode)
|
||||
) {
|
||||
parentNode.insertBefore(oldEndNode, oldStartNode);
|
||||
diff(oldEndNode, newStartNode, replayer, rrnodeMirror);
|
||||
oldEndNode = oldChildren[--oldEndIndex];
|
||||
newStartNode = newChildren[++newStartIndex];
|
||||
} else {
|
||||
if (!oldIdToIndex) {
|
||||
oldIdToIndex = {};
|
||||
for (let i = oldStartIndex; i <= oldEndIndex; i++) {
|
||||
const oldChild = oldChildren[i];
|
||||
if (oldChild && replayer.mirror.hasNode(oldChild))
|
||||
oldIdToIndex[replayer.mirror.getId(oldChild)] = i;
|
||||
}
|
||||
}
|
||||
indexInOld = oldIdToIndex[rrnodeMirror.getId(newStartNode)];
|
||||
if (indexInOld) {
|
||||
const nodeToMove = oldChildren[indexInOld]!;
|
||||
parentNode.insertBefore(nodeToMove, oldStartNode);
|
||||
diff(nodeToMove, newStartNode, replayer, rrnodeMirror);
|
||||
oldChildren[indexInOld] = undefined;
|
||||
} else {
|
||||
const newNode = createOrGetNode(
|
||||
newStartNode,
|
||||
replayer.mirror,
|
||||
rrnodeMirror,
|
||||
);
|
||||
|
||||
/**
|
||||
* A mounted iframe element has an automatically created HTML element.
|
||||
* We should delete it before insert a serialized one. Otherwise, an error 'Only one element on document allowed' will be thrown.
|
||||
*/
|
||||
if (
|
||||
replayer.mirror.getMeta(parentNode)?.type === RRNodeType.Document &&
|
||||
replayer.mirror.getMeta(newNode)?.type === RRNodeType.Element &&
|
||||
((parentNode as Node) as Document).documentElement
|
||||
) {
|
||||
parentNode.removeChild(
|
||||
((parentNode as Node) as Document).documentElement,
|
||||
);
|
||||
oldChildren[oldStartIndex] = undefined;
|
||||
oldStartNode = undefined;
|
||||
}
|
||||
parentNode.insertBefore(newNode, oldStartNode || null);
|
||||
diff(newNode, newStartNode, replayer, rrnodeMirror);
|
||||
}
|
||||
newStartNode = newChildren[++newStartIndex];
|
||||
}
|
||||
}
|
||||
if (oldStartIndex > oldEndIndex) {
|
||||
const referenceRRNode = newChildren[newEndIndex + 1];
|
||||
let referenceNode = null;
|
||||
if (referenceRRNode)
|
||||
parentNode.childNodes.forEach((child) => {
|
||||
if (
|
||||
replayer.mirror.getId(child) === rrnodeMirror.getId(referenceRRNode)
|
||||
)
|
||||
referenceNode = child;
|
||||
});
|
||||
for (; newStartIndex <= newEndIndex; ++newStartIndex) {
|
||||
const newNode = createOrGetNode(
|
||||
newChildren[newStartIndex],
|
||||
replayer.mirror,
|
||||
rrnodeMirror,
|
||||
);
|
||||
parentNode.insertBefore(newNode, referenceNode);
|
||||
diff(newNode, newChildren[newStartIndex], replayer, rrnodeMirror);
|
||||
}
|
||||
} else if (newStartIndex > newEndIndex) {
|
||||
for (; oldStartIndex <= oldEndIndex; oldStartIndex++) {
|
||||
const node = oldChildren[oldStartIndex];
|
||||
if (node) {
|
||||
parentNode.removeChild(node);
|
||||
replayer.mirror.removeNodeFromMap(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createOrGetNode(
|
||||
rrNode: IRRNode,
|
||||
domMirror: NodeMirror,
|
||||
rrnodeMirror: Mirror,
|
||||
): Node {
|
||||
let node = domMirror.getNode(rrnodeMirror.getId(rrNode));
|
||||
const sn = rrnodeMirror.getMeta(rrNode);
|
||||
if (node !== null) return node;
|
||||
switch (rrNode.RRNodeType) {
|
||||
case RRNodeType.Document:
|
||||
node = new Document();
|
||||
break;
|
||||
case RRNodeType.DocumentType:
|
||||
node = document.implementation.createDocumentType(
|
||||
(rrNode as IRRDocumentType).name,
|
||||
(rrNode as IRRDocumentType).publicId,
|
||||
(rrNode as IRRDocumentType).systemId,
|
||||
);
|
||||
break;
|
||||
case RRNodeType.Element:
|
||||
let tagName = (rrNode as IRRElement).tagName.toLowerCase();
|
||||
tagName = SVGTagMap[tagName] || tagName;
|
||||
if (sn && 'isSVG' in sn && sn?.isSVG) {
|
||||
node = document.createElementNS(
|
||||
NAMESPACES['svg'],
|
||||
(rrNode as IRRElement).tagName.toLowerCase(),
|
||||
);
|
||||
} else node = document.createElement((rrNode as IRRElement).tagName);
|
||||
break;
|
||||
case RRNodeType.Text:
|
||||
node = document.createTextNode((rrNode as IRRText).data);
|
||||
break;
|
||||
case RRNodeType.Comment:
|
||||
node = document.createComment((rrNode as IRRComment).data);
|
||||
break;
|
||||
case RRNodeType.CDATA:
|
||||
node = document.createCDATASection((rrNode as IRRCDATASection).data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (sn) domMirror.add(node, { ...sn });
|
||||
return node;
|
||||
}
|
||||
|
||||
export function getNestedRule(
|
||||
rules: CSSRuleList,
|
||||
position: number[],
|
||||
): CSSGroupingRule {
|
||||
const rule = rules[position[0]] as CSSGroupingRule;
|
||||
if (position.length === 1) {
|
||||
return rule;
|
||||
} else {
|
||||
return getNestedRule(
|
||||
((rule as CSSGroupingRule).cssRules[position[1]] as CSSGroupingRule)
|
||||
.cssRules,
|
||||
position.slice(2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export enum StyleRuleType {
|
||||
Insert,
|
||||
Remove,
|
||||
Snapshot,
|
||||
SetProperty,
|
||||
RemoveProperty,
|
||||
}
|
||||
type InsertRule = {
|
||||
cssText: string;
|
||||
type: StyleRuleType.Insert;
|
||||
index?: number | number[];
|
||||
};
|
||||
type RemoveRule = {
|
||||
type: StyleRuleType.Remove;
|
||||
index: number | number[];
|
||||
};
|
||||
type SetPropertyRule = {
|
||||
type: StyleRuleType.SetProperty;
|
||||
index: number[];
|
||||
property: string;
|
||||
value: string | null;
|
||||
priority: string | undefined;
|
||||
};
|
||||
type RemovePropertyRule = {
|
||||
type: StyleRuleType.RemoveProperty;
|
||||
index: number[];
|
||||
property: string;
|
||||
};
|
||||
|
||||
export type VirtualStyleRules = Array<
|
||||
InsertRule | RemoveRule | SetPropertyRule | RemovePropertyRule
|
||||
>;
|
||||
|
||||
export function getPositionsAndIndex(nestedIndex: number[]) {
|
||||
const positions = [...nestedIndex];
|
||||
const index = positions.pop();
|
||||
return { positions, index };
|
||||
}
|
||||
|
||||
export function applyVirtualStyleRulesToNode(
|
||||
styleNode: HTMLStyleElement,
|
||||
virtualStyleRules: VirtualStyleRules,
|
||||
) {
|
||||
const sheet = styleNode.sheet!;
|
||||
|
||||
virtualStyleRules.forEach((rule) => {
|
||||
if (rule.type === StyleRuleType.Insert) {
|
||||
try {
|
||||
if (Array.isArray(rule.index)) {
|
||||
const { positions, index } = getPositionsAndIndex(rule.index);
|
||||
const nestedRule = getNestedRule(sheet.cssRules, positions);
|
||||
nestedRule.insertRule(rule.cssText, index);
|
||||
} else {
|
||||
sheet.insertRule(rule.cssText, rule.index);
|
||||
}
|
||||
} catch (e) {
|
||||
/**
|
||||
* sometimes we may capture rules with browser prefix
|
||||
* insert rule with prefixs in other browsers may cause Error
|
||||
*/
|
||||
}
|
||||
} else if (rule.type === StyleRuleType.Remove) {
|
||||
try {
|
||||
if (Array.isArray(rule.index)) {
|
||||
const { positions, index } = getPositionsAndIndex(rule.index);
|
||||
const nestedRule = getNestedRule(sheet.cssRules, positions);
|
||||
nestedRule.deleteRule(index || 0);
|
||||
} else {
|
||||
sheet.deleteRule(rule.index);
|
||||
}
|
||||
} catch (e) {
|
||||
/**
|
||||
* accessing styleSheet rules may cause SecurityError
|
||||
* for specific access control settings
|
||||
*/
|
||||
}
|
||||
} else if (rule.type === StyleRuleType.SetProperty) {
|
||||
const nativeRule = (getNestedRule(
|
||||
sheet.cssRules,
|
||||
rule.index,
|
||||
) as unknown) as CSSStyleRule;
|
||||
nativeRule.style.setProperty(rule.property, rule.value, rule.priority);
|
||||
} else if (rule.type === StyleRuleType.RemoveProperty) {
|
||||
const nativeRule = (getNestedRule(
|
||||
sheet.cssRules,
|
||||
rule.index,
|
||||
) as unknown) as CSSStyleRule;
|
||||
nativeRule.style.removeProperty(rule.property);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user