feat(frontend/player): adoptedStyleSheets support

This commit is contained in:
Alex Kaminskii 2022-08-09 20:33:52 +02:00
parent 53bb440987
commit 0013967376
6 changed files with 354 additions and 108 deletions

View file

@ -24,10 +24,32 @@ const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/; // regexp costs ~
// .replace(/\-webkit\-/g, "")
// }
function insertRule(sheet: CSSStyleSheet, msg: { rule: string, index: number }) {
try {
sheet.insertRule(msg.rule, msg.index)
} catch (e) {
logger.warn(e, msg)
try {
sheet.insertRule(msg.rule)
} catch (e) {
logger.warn("Cannot insert rule.", e, msg)
}
}
}
function deleteRule(sheet: CSSStyleSheet, msg: { index: number }) {
try {
sheet.deleteRule(msg.index)
} catch (e) {
logger.warn(e, msg)
}
}
export default class DOMManager extends ListWalker<Message> {
private vTexts: Map<number, VText> = new Map() // map vs object here?
private vElements: Map<number, VElement> = new Map()
private vRoots: Map<number, VFragment | VDocument> = new Map()
private styleSheets: Map<number, CSSStyleSheet> = new Map()
private upperBodyId: number = -1;
@ -116,6 +138,7 @@ export default class DOMManager extends ListWalker<Message> {
let node: Node | undefined
let vn: VNode | undefined
let doc: Document | null
let styleSheet: CSSStyleSheet | undefined
switch (msg.tp) {
case "create_document":
doc = this.screen.document;
@ -133,7 +156,9 @@ export default class DOMManager extends ListWalker<Message> {
this.vElements = new Map([[0, vn]])
const vDoc = new VDocument(doc)
vDoc.insertChildAt(vn, 0)
this.vRoots = new Map([[-1, vDoc]]) // todo: start from 0 (sync logic with tracker)
this.vRoots = new Map([[0, vDoc]]) // watchout: id==0 for both Document and documentElement
// this is done for the AdoptedCSS logic
// todo: start from 0 (sync logic with tracker)
this.stylesManager.reset()
return
case "create_text_node":
@ -241,18 +266,7 @@ export default class DOMManager extends ListWalker<Message> {
logger.warn("Non-style node in CSS rules message (or sheet is null)", msg, vn);
return
}
vn.onStyleSheet(sheet => {
try {
sheet.insertRule(msg.rule, msg.index)
} catch (e) {
logger.warn(e, msg)
try {
sheet.insertRule(msg.rule)
} catch (e) {
logger.warn("Cannot insert rule.", e, msg)
}
}
})
vn.onStyleSheet(sheet => insertRule(sheet, msg))
return
case "css_delete_rule":
vn = this.vElements.get(msg.id)
@ -261,13 +275,7 @@ export default class DOMManager extends ListWalker<Message> {
logger.warn("Non-style node in CSS rules message (or sheet is null)", msg, vn);
return
}
vn.onStyleSheet(sheet => {
try {
sheet.deleteRule(msg.index)
} catch (e) {
logger.warn(e, msg)
}
})
vn.onStyleSheet(sheet => deleteRule(sheet, msg))
return
case "create_i_frame_document":
vn = this.vElements.get(msg.frameID)
@ -298,6 +306,53 @@ export default class DOMManager extends ListWalker<Message> {
logger.warn("Context message host is not Element", msg)
}
return
case "adopted_ss_insert_rule":
styleSheet = this.styleSheets.get(msg.sheetID)
if (!styleSheet) {
styleSheet = new CSSStyleSheet()
this.styleSheets.set(msg.sheetID, styleSheet)
}
insertRule(styleSheet, msg)
return
case "adopted_ss_delete_rule":
styleSheet = this.styleSheets.get(msg.sheetID)
if (!styleSheet) {
logger.warn("No stylesheet was created for ", msg)
return
}
deleteRule(styleSheet, msg)
return
case "adopted_ss_replace":
styleSheet = this.styleSheets.get(msg.sheetID)
if (!styleSheet) {
logger.warn("No stylesheet was created for ", msg)
return
}
// @ts-ignore
styleSheet.replaceSync(msg.text)
return
case "adopted_ss_add_owner":
styleSheet = this.styleSheets.get(msg.sheetID)
if (!styleSheet) {
logger.warn("No stylesheet was created for ", msg)
return
}
vn = this.vRoots.get(msg.id)
if (!vn) { logger.error("Node not found", msg); return }
//@ts-ignore
vn.node.adoptedStyleSheets = [...vn.node.adoptedStyleSheets, styleSheet]
return
case "adopted_ss_remove_owner":
styleSheet = this.styleSheets.get(msg.sheetID)
if (!styleSheet) {
logger.warn("No stylesheet was created for ", msg)
return
}
vn = this.vRoots.get(msg.id)
if (!vn) { logger.error("Node not found", msg); return }
//@ts-ignore
vn.node.adoptedStyleSheets = [...vn.node.adoptedStyleSheets].filter(s => s !== styleSheet)
return
}
}

View file

@ -1,6 +1,63 @@
import type { RawMessage } from './raw'
import type {
RawMessage,
RawSetNodeAttributeURLBased,
RawSetNodeAttribute,
RawSetCssDataURLBased,
RawSetCssData,
RawCssInsertRuleURLBased,
RawCssInsertRule,
RawAdoptedSsInsertRuleURLBased,
RawAdoptedSsInsertRule,
RawAdoptedSsReplaceURLBased,
RawAdoptedSsReplace,
} from './raw'
import { TP_MAP } from './raw'
import { resolveURL, resolveCSS } from './urlResolve'
// TODO: commonURLBased logic
const resolvers = {
"set_node_attribute_url_based": (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute =>
({
...msg,
value: msg.name === 'src' || msg.name === 'href'
? resolveURL(msg.baseURL, msg.value)
: (msg.name === 'style'
? resolveCSS(msg.baseURL, msg.value)
: msg.value
),
tp: "set_node_attribute",
}),
"set_css_data_url_based": (msg: RawSetCssDataURLBased): RawSetCssData =>
({
...msg,
data: resolveCSS(msg.baseURL, msg.data),
tp: "set_css_data",
}),
"css_insert_rule_url_based": (msg: RawCssInsertRuleURLBased): RawCssInsertRule =>
({
...msg,
rule: resolveCSS(msg.baseURL, msg.rule),
tp: "css_insert_rule",
}),
"adopted_ss_insert_rule_url_based": (msg: RawAdoptedSsInsertRuleURLBased): RawAdoptedSsInsertRule =>
({
...msg,
rule: resolveCSS(msg.baseURL, msg.rule),
tp: "adopted_ss_insert_rule",
}),
"adopted_ss_replace_url_based": (msg: RawAdoptedSsReplaceURLBased): RawAdoptedSsReplace =>
({
...msg,
text: resolveCSS(msg.baseURL, msg.text),
tp: "adopted_ss_replace"
})
} as const
type ResolvingTypes = keyof typeof resolvers
type Resolver = typeof resolvers[ResolvingTypes]
type NumTypes = keyof typeof TP_MAP
export default class JSONRawMessageReader {
constructor(private messages: any[] = []){}
@ -8,11 +65,21 @@ export default class JSONRawMessageReader {
this.messages = this.messages.concat(messages)
}
readMessage(): RawMessage | null {
const msg = this.messages.shift()
let msg = this.messages.shift()
if (!msg) { return null }
msg.tp = TP_MAP[msg._id]
const type = TP_MAP[msg._id as NumTypes]
if (!type) { // Ignore unknown message types
// log here
return this.readMessage()
}
msg.tp = type
delete msg._id
return msg as RawMessage
const resolver: Resolver | undefined = resolvers[type as ResolvingTypes]
if (resolver) {
msg = resolver(msg)
}
return msg as RawMessage
}
}

View file

@ -1,68 +1,25 @@
import type { Message } from './message'
import type {
RawMessage,
RawSetNodeAttributeURLBased,
RawSetNodeAttribute,
RawSetCssDataURLBased,
RawSetCssData,
RawCssInsertRuleURLBased,
RawCssInsertRule,
} from './raw'
import type { RawMessage } from './raw'
import RawMessageReader from './RawMessageReader'
import { resolveURL, resolveCSS } from './urlResolve'
interface RawMessageReaderI {
readMessage(): RawMessage | null
}
const resolveMsg = {
"set_node_attribute_url_based": (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute =>
({
...msg,
value: msg.name === 'src' || msg.name === 'href'
? resolveURL(msg.baseURL, msg.value)
: (msg.name === 'style'
? resolveCSS(msg.baseURL, msg.value)
: msg.value
),
tp: "set_node_attribute",
}),
"set_css_data_url_based": (msg: RawSetCssDataURLBased): RawSetCssData =>
({
...msg,
data: resolveCSS(msg.baseURL, msg.data),
tp: "set_css_data",
}),
"css_insert_rule_url_based": (msg: RawCssInsertRuleURLBased): RawCssInsertRule =>
({
...msg,
rule: resolveCSS(msg.baseURL, msg.rule),
tp: "css_insert_rule",
})
}
export default class MStreamReader {
constructor(private readonly r: RawMessageReaderI = new RawMessageReader()){}
constructor(private readonly r: RawMessageReaderI = new RawMessageReader(), private startTs: number = 0){}
// append(buf: Uint8Array) {
// this.r.append(buf)
// }
private t0: number = 0
private t: number = 0
private idx: number = 0
readNext(): Message | null {
let msg = this.r.readMessage()
if (msg === null) { return null }
if (msg.tp === "timestamp" || msg.tp === "batch_meta") {
this.t0 = this.t0 || msg.timestamp
this.t = msg.timestamp - this.t0
if (msg.tp === "timestamp") {
this.startTs = this.startTs || msg.timestamp
this.t = msg.timestamp - this.startTs
return this.readNext()
}
// why typescript doesn't work here?
msg = (resolveMsg[msg.tp] || ((m:RawMessage)=>m))(msg)
return Object.assign(msg, {
time: this.t,
_index: this.idx++,

View file

@ -29,6 +29,32 @@ export default class RawMessageReader extends PrimitiveReader {
};
}
case 81: {
const version = this.readUint(); if (version === null) { return resetPointer() }
const pageNo = this.readUint(); if (pageNo === null) { return resetPointer() }
const firstIndex = this.readUint(); if (firstIndex === null) { return resetPointer() }
const timestamp = this.readInt(); if (timestamp === null) { return resetPointer() }
const location = this.readString(); if (location === null) { return resetPointer() }
return {
tp: "batch_metadata",
version,
pageNo,
firstIndex,
timestamp,
location,
};
}
case 82: {
const partNo = this.readUint(); if (partNo === null) { return resetPointer() }
const partTotal = this.readUint(); if (partTotal === null) { return resetPointer() }
return {
tp: "partitioned_message",
partNo,
partTotal,
};
}
case 0: {
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
return {
@ -37,14 +63,6 @@ export default class RawMessageReader extends PrimitiveReader {
};
}
case 2: {
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
return {
tp: "session_disconnect",
timestamp,
};
}
case 4: {
const url = this.readString(); if (url === null) { return resetPointer() }
const referrer = this.readString(); if (referrer === null) { return resetPointer() }
@ -575,14 +593,6 @@ export default class RawMessageReader extends PrimitiveReader {
};
}
case 65: {
return {
tp: "page_close",
};
}
case 67: {
const id = this.readUint(); if (id === null) { return resetPointer() }
const rule = this.readString(); if (rule === null) { return resetPointer() }
@ -621,6 +631,84 @@ export default class RawMessageReader extends PrimitiveReader {
};
}
case 71: {
const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() }
const text = this.readString(); if (text === null) { return resetPointer() }
const baseURL = this.readString(); if (baseURL === null) { return resetPointer() }
return {
tp: "adopted_ss_replace_url_based",
sheetID,
text,
baseURL,
};
}
case 72: {
const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() }
const text = this.readString(); if (text === null) { return resetPointer() }
return {
tp: "adopted_ss_replace",
sheetID,
text,
};
}
case 73: {
const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() }
const rule = this.readString(); if (rule === null) { return resetPointer() }
const index = this.readUint(); if (index === null) { return resetPointer() }
const baseURL = this.readString(); if (baseURL === null) { return resetPointer() }
return {
tp: "adopted_ss_insert_rule_url_based",
sheetID,
rule,
index,
baseURL,
};
}
case 74: {
const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() }
const rule = this.readString(); if (rule === null) { return resetPointer() }
const index = this.readUint(); if (index === null) { return resetPointer() }
return {
tp: "adopted_ss_insert_rule",
sheetID,
rule,
index,
};
}
case 75: {
const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() }
const index = this.readUint(); if (index === null) { return resetPointer() }
return {
tp: "adopted_ss_delete_rule",
sheetID,
index,
};
}
case 76: {
const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() }
const id = this.readUint(); if (id === null) { return resetPointer() }
return {
tp: "adopted_ss_add_owner",
sheetID,
id,
};
}
case 77: {
const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() }
const id = this.readUint(); if (id === null) { return resetPointer() }
return {
tp: "adopted_ss_remove_owner",
sheetID,
id,
};
}
case 90: {
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
const projectID = this.readUint(); if (projectID === null) { return resetPointer() }

View file

@ -4,8 +4,9 @@ import type { Timed } from './timed'
import type { RawMessage } from './raw'
import type {
RawBatchMeta,
RawBatchMetadata,
RawPartitionedMessage,
RawTimestamp,
RawSessionDisconnect,
RawSetPageLocation,
RawSetViewportSize,
RawSetViewportScroll,
@ -51,10 +52,16 @@ import type {
RawSetCssDataURLBased,
RawTechnicalInfo,
RawCustomIssue,
RawPageClose,
RawCssInsertRuleURLBased,
RawMouseClick,
RawCreateIFrameDocument,
RawAdoptedSsReplaceURLBased,
RawAdoptedSsReplace,
RawAdoptedSsInsertRuleURLBased,
RawAdoptedSsInsertRule,
RawAdoptedSsDeleteRule,
RawAdoptedSsAddOwner,
RawAdoptedSsRemoveOwner,
RawIosSessionStart,
RawIosCustomEvent,
RawIosScreenChanges,
@ -69,9 +76,11 @@ export type Message = RawMessage & Timed
export type BatchMeta = RawBatchMeta & Timed
export type Timestamp = RawTimestamp & Timed
export type BatchMetadata = RawBatchMetadata & Timed
export type SessionDisconnect = RawSessionDisconnect & Timed
export type PartitionedMessage = RawPartitionedMessage & Timed
export type Timestamp = RawTimestamp & Timed
export type SetPageLocation = RawSetPageLocation & Timed
@ -163,14 +172,26 @@ export type TechnicalInfo = RawTechnicalInfo & Timed
export type CustomIssue = RawCustomIssue & Timed
export type PageClose = RawPageClose & Timed
export type CssInsertRuleURLBased = RawCssInsertRuleURLBased & Timed
export type MouseClick = RawMouseClick & Timed
export type CreateIFrameDocument = RawCreateIFrameDocument & Timed
export type AdoptedSsReplaceURLBased = RawAdoptedSsReplaceURLBased & Timed
export type AdoptedSsReplace = RawAdoptedSsReplace & Timed
export type AdoptedSsInsertRuleURLBased = RawAdoptedSsInsertRuleURLBased & Timed
export type AdoptedSsInsertRule = RawAdoptedSsInsertRule & Timed
export type AdoptedSsDeleteRule = RawAdoptedSsDeleteRule & Timed
export type AdoptedSsAddOwner = RawAdoptedSsAddOwner & Timed
export type AdoptedSsRemoveOwner = RawAdoptedSsRemoveOwner & Timed
export type IosSessionStart = RawIosSessionStart & Timed
export type IosCustomEvent = RawIosCustomEvent & Timed

View file

@ -2,8 +2,9 @@
export const TP_MAP = {
80: "batch_meta",
81: "batch_metadata",
82: "partitioned_message",
0: "timestamp",
2: "session_disconnect",
4: "set_page_location",
5: "set_viewport_size",
6: "set_viewport_scroll",
@ -49,10 +50,16 @@ export const TP_MAP = {
61: "set_css_data_url_based",
63: "technical_info",
64: "custom_issue",
65: "page_close",
67: "css_insert_rule_url_based",
69: "mouse_click",
70: "create_i_frame_document",
71: "adopted_ss_replace_url_based",
72: "adopted_ss_replace",
73: "adopted_ss_insert_rule_url_based",
74: "adopted_ss_insert_rule",
75: "adopted_ss_delete_rule",
76: "adopted_ss_add_owner",
77: "adopted_ss_remove_owner",
90: "ios_session_start",
93: "ios_custom_event",
96: "ios_screen_changes",
@ -60,7 +67,7 @@ export const TP_MAP = {
102: "ios_performance_event",
103: "ios_log",
105: "ios_network_call",
}
} as const
export interface RawBatchMeta {
@ -70,13 +77,23 @@ export interface RawBatchMeta {
timestamp: number,
}
export interface RawTimestamp {
tp: "timestamp",
export interface RawBatchMetadata {
tp: "batch_metadata",
version: number,
pageNo: number,
firstIndex: number,
timestamp: number,
location: string,
}
export interface RawSessionDisconnect {
tp: "session_disconnect",
export interface RawPartitionedMessage {
tp: "partitioned_message",
partNo: number,
partTotal: number,
}
export interface RawTimestamp {
tp: "timestamp",
timestamp: number,
}
@ -390,11 +407,6 @@ export interface RawCustomIssue {
payload: string,
}
export interface RawPageClose {
tp: "page_close",
}
export interface RawCssInsertRuleURLBased {
tp: "css_insert_rule_url_based",
id: number,
@ -417,6 +429,52 @@ export interface RawCreateIFrameDocument {
id: number,
}
export interface RawAdoptedSsReplaceURLBased {
tp: "adopted_ss_replace_url_based",
sheetID: number,
text: string,
baseURL: string,
}
export interface RawAdoptedSsReplace {
tp: "adopted_ss_replace",
sheetID: number,
text: string,
}
export interface RawAdoptedSsInsertRuleURLBased {
tp: "adopted_ss_insert_rule_url_based",
sheetID: number,
rule: string,
index: number,
baseURL: string,
}
export interface RawAdoptedSsInsertRule {
tp: "adopted_ss_insert_rule",
sheetID: number,
rule: string,
index: number,
}
export interface RawAdoptedSsDeleteRule {
tp: "adopted_ss_delete_rule",
sheetID: number,
index: number,
}
export interface RawAdoptedSsAddOwner {
tp: "adopted_ss_add_owner",
sheetID: number,
id: number,
}
export interface RawAdoptedSsRemoveOwner {
tp: "adopted_ss_remove_owner",
sheetID: number,
id: number,
}
export interface RawIosSessionStart {
tp: "ios_session_start",
timestamp: number,
@ -488,4 +546,4 @@ export interface RawIosNetworkCall {
}
export type RawMessage = RawBatchMeta | RawTimestamp | RawSessionDisconnect | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputTarget | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawConsoleLog | RawPageLoadTiming | RawPageRenderTiming | RawJsException | RawRawCustomEvent | RawUserID | RawUserAnonymousID | RawMetadata | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawStateAction | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawResourceTiming | RawConnectionInformation | RawSetPageVisibility | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawTechnicalInfo | RawCustomIssue | RawPageClose | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall;
export type RawMessage = RawBatchMeta | RawBatchMetadata | RawPartitionedMessage | RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputTarget | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawConsoleLog | RawPageLoadTiming | RawPageRenderTiming | RawJsException | RawRawCustomEvent | RawUserID | RawUserAnonymousID | RawMetadata | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawStateAction | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawResourceTiming | RawConnectionInformation | RawSetPageVisibility | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawTechnicalInfo | RawCustomIssue | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall;