frontend (feat): maintaining of iframes in replay & hover styles rewriting

This commit is contained in:
ShiKhu 2021-09-23 18:06:18 +02:00
parent 2ebdd3d67f
commit 43862e32e1
3 changed files with 171 additions and 5 deletions

View file

@ -3,7 +3,7 @@ import type { Message, SetNodeScroll, CreateElementNode } from '../messages';
import type { TimedMessage } from '../Timed';
import logger from 'App/logger';
import StylesManager from './StylesManager';
import StylesManager, { rewriteNodeStyleSheet } from './StylesManager';
import ListWalker from './ListWalker';
import type { Timed }from '../Timed';
@ -147,6 +147,7 @@ export default class DOMManager extends ListWalker<TimedMessage> {
this.insertNode(msg);
break;
case "create_element_node":
// console.log('elementnode', msg)
if (msg.svg) {
this.nl[ msg.id ] = document.createElementNS('http://www.w3.org/2000/svg', msg.tag);
} else {
@ -207,9 +208,14 @@ export default class DOMManager extends ListWalker<TimedMessage> {
break;
case "set_node_data":
case "set_css_data":
if (!this.nl[ msg.id ]) { logger.error("Node not found", msg); break; }
node = this.nl[ msg.id ]
if (!node) { logger.error("Node not found", msg); break; }
// @ts-ignore
this.nl[ msg.id ].data = msg.data;
node.data = msg.data;
if (node instanceof HTMLStyleElement) {
const doc = this.screen.document
doc && rewriteNodeStyleSheet(doc, node)
}
break;
case "css_insert_rule":
node = this.nl[ msg.id ];
@ -239,6 +245,23 @@ export default class DOMManager extends ListWalker<TimedMessage> {
} catch (e) {
logger.warn(e, msg)
}
break;
case "create_i_frame_document":
// console.log('ifr', msg)
node = this.nl[ msg.frameID ];
if (!(node instanceof HTMLIFrameElement)) {
logger.warn("create_i_frame_document message. Node is not iframe")
return;
}
console.log("iframe", msg)
// await new Promise(resolve => { node.onload = resolve })
const doc = node.contentDocument;
if (!doc) {
logger.warn("No iframe doc", msg, node, node.contentDocument);
return;
}
this.nl[ msg.id ] = doc.documentElement
break;
//not sure what to do with this one
//case "disconnected":

View file

@ -0,0 +1,109 @@
import type StatedScreen from '../StatedScreen';
import type { CssInsertRule, CssDeleteRule } from '../messages';
import type { Timed } from '../Timed';
type CSSRuleMessage = CssInsertRule | CssDeleteRule;
type TimedCSSRuleMessage = Timed & CSSRuleMessage;
import logger from 'App/logger';
import ListWalker from './ListWalker';
const HOVER_CN = "-openreplay-hover";
const HOVER_SELECTOR = `.${HOVER_CN}`;
// Doesn't work with css files (hasOwnProperty)
export function rewriteNodeStyleSheet(doc: Document, node: HTMLLinkElement | HTMLStyleElement) {
const ss = Object.values(doc.styleSheets).find(s => s.ownerNode === node);
if (!ss || !ss.hasOwnProperty('rules')) { return; }
for(let i = 0; i < ss.rules.length; i++){
const r = ss.rules[i]
if (r instanceof CSSStyleRule) {
r.selectorText = r.selectorText.replace('/\:hover/g', HOVER_SELECTOR)
}
}
}
export default class StylesManager extends ListWalker<TimedCSSRuleMessage> {
private linkLoadingCount: number = 0;
private linkLoadPromises: Array<Promise<void>> = [];
private skipCSSLinks: Array<string> = []; // should be common for all pages
constructor(private readonly screen: StatedScreen) {
super();
}
reset():void {
super.reset();
this.linkLoadingCount = 0;
this.linkLoadPromises = [];
//cancel all promises? tothinkaboutit
}
setStyleHandlers(node: HTMLLinkElement, value: string): void {
let timeoutId;
const promise = new Promise((resolve) => {
if (this.skipCSSLinks.includes(value)) resolve(null);
this.linkLoadingCount++;
this.screen.setCSSLoading(true);
const addSkipAndResolve = () => {
this.skipCSSLinks.push(value); // watch out
resolve(null);
}
timeoutId = setTimeout(addSkipAndResolve, 4000);
node.onload = () => {
const doc = this.screen.document;
doc && rewriteNodeStyleSheet(doc, node);
resolve(null);
}
node.onerror = addSkipAndResolve;
}).then(() => {
node.onload = null;
node.onerror = null;
clearTimeout(timeoutId);
this.linkLoadingCount--;
if (this.linkLoadingCount === 0) {
this.screen.setCSSLoading(false);
}
});
this.linkLoadPromises.push(promise);
}
private manageRule = (msg: CSSRuleMessage):void => {
// if (msg.tp === "css_insert_rule") {
// let styleSheet = this.#screen.document.styleSheets[ msg.stylesheetID ];
// if (!styleSheet) {
// logger.log("No stylesheet with corresponding ID found: ", msg)
// styleSheet = this.#screen.document.styleSheets[0];
// if (!styleSheet) {
// return;
// }
// }
// try {
// styleSheet.insertRule(msg.rule, msg.index);
// } catch (e) {
// logger.log(e, msg)
// //const index = Math.min(msg.index, styleSheet.cssRules.length);
// styleSheet.insertRule(msg.rule, styleSheet.cssRules.length);
// //styleSheet.ownerNode.innerHTML += msg.rule;
// }
// }
// if (msg.tp === "css_delete_rule") {
// // console.warn('Warning: STYLESHEET_DELETE_RULE msg')
// const styleSheet = this.#screen.document.styleSheets[msg.stylesheetID];
// if (!styleSheet) {
// logger.log("No stylesheet with corresponding ID found: ", msg)
// return;
// }
// styleSheet.deleteRule(msg.index);
// }
}
moveReady(t: number): Promise<void> {
return Promise.all(this.linkLoadPromises)
.then(() => this.moveApply(t, this.manageRule));
}
}

View file

@ -36,6 +36,8 @@ export const ID_TP_MAP = {
54: "connection_information",
55: "set_page_visibility",
59: "long_task",
69: "mouse_click",
70: "create_i_frame_document",
} as const;
@ -255,11 +257,26 @@ export interface LongTask {
containerName: string,
}
export interface MouseClick {
tp: "mouse_click",
id: number,
hesitationTime: number,
label: string,
selector: string,
}
export type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetCssData | SetNodeScroll | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | CssInsertRule | CssDeleteRule | Fetch | Profiler | OTable | Redux | Vuex | MobX | NgRx | GraphQl | PerformanceTrack | ConnectionInformation | SetPageVisibility | LongTask;
export interface CreateIFrameDocument {
tp: "create_i_frame_document",
frameID: number,
id: number,
}
export type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetCssData | SetNodeScroll | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | CssInsertRule | CssDeleteRule | Fetch | Profiler | OTable | Redux | Vuex | MobX | NgRx | GraphQl | PerformanceTrack | ConnectionInformation | SetPageVisibility | LongTask | MouseClick | CreateIFrameDocument;
export default function (r: PrimitiveReader): Message | null {
switch (r.readUint()) {
const ui= r.readUint()
switch (ui) {
case 0:
return {
@ -509,7 +526,24 @@ export default function (r: PrimitiveReader): Message | null {
containerName: r.readString(),
};
case 69:
return {
tp: ID_TP_MAP[69],
id: r.readUint(),
hesitationTime: r.readUint(),
label: r.readString(),
selector: r.readString(),
};
case 70:
return {
tp: ID_TP_MAP[70],
frameID: r.readUint(),
id: r.readUint(),
};
default:
console.log("wtf is this", ui)
r.readUint(); // IOS skip timestamp
r.skip(r.readUint());
return null;