Dev aleksk (#795)
*feat(tracker;backend;frontend/player): focus in elem *feat(tracker;frontend/player): FontFace load
This commit is contained in:
parent
d8774122b7
commit
822ca53980
28 changed files with 3070 additions and 2713 deletions
|
|
@ -10,5 +10,5 @@ func IsIOSType(id int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsDOMType(id int) bool {
|
func IsDOMType(id int) bool {
|
||||||
return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 54 == id || 55 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id
|
return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -66,12 +66,18 @@ func ResolveCSS(baseURL string, css string) string {
|
||||||
css = rewriteLinks(css, func(rawurl string) string {
|
css = rewriteLinks(css, func(rawurl string) string {
|
||||||
return ResolveURL(baseURL, rawurl)
|
return ResolveURL(baseURL, rawurl)
|
||||||
})
|
})
|
||||||
return strings.Replace(css, ":hover", ".-openreplay-hover", -1)
|
return rewritePseudoclasses(css)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Rewriter) RewriteCSS(sessionID uint64, baseurl string, css string) string {
|
func (r *Rewriter) RewriteCSS(sessionID uint64, baseurl string, css string) string {
|
||||||
css = rewriteLinks(css, func(rawurl string) string {
|
css = rewriteLinks(css, func(rawurl string) string {
|
||||||
return r.RewriteURL(sessionID, baseurl, rawurl)
|
return r.RewriteURL(sessionID, baseurl, rawurl)
|
||||||
})
|
})
|
||||||
return strings.Replace(css, ":hover", ".-openreplay-hover", -1)
|
return rewritePseudoclasses(css)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rewritePseudoclasses(css string) string {
|
||||||
|
css = strings.Replace(css, ":hover", ".-openreplay-hover", -1)
|
||||||
|
css = strings.Replace(css, ":focus", ".-openreplay-focus", -1)
|
||||||
|
return css
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -568,6 +568,23 @@ class PerformanceTrackAggr(Message):
|
||||||
self.max_used_js_heap_size = max_used_js_heap_size
|
self.max_used_js_heap_size = max_used_js_heap_size
|
||||||
|
|
||||||
|
|
||||||
|
class LoadFontFace(Message):
|
||||||
|
__id__ = 57
|
||||||
|
|
||||||
|
def __init__(self, parent_id, family, source, descriptors):
|
||||||
|
self.parent_id = parent_id
|
||||||
|
self.family = family
|
||||||
|
self.source = source
|
||||||
|
self.descriptors = descriptors
|
||||||
|
|
||||||
|
|
||||||
|
class SetNodeFocus(Message):
|
||||||
|
__id__ = 58
|
||||||
|
|
||||||
|
def __init__(self, id):
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
|
||||||
class LongTask(Message):
|
class LongTask(Message):
|
||||||
__id__ = 59
|
__id__ = 59
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -524,6 +524,19 @@ class MessageCodec(Codec):
|
||||||
max_used_js_heap_size=self.read_uint(reader)
|
max_used_js_heap_size=self.read_uint(reader)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if message_id == 57:
|
||||||
|
return LoadFontFace(
|
||||||
|
parent_id=self.read_uint(reader),
|
||||||
|
family=self.read_string(reader),
|
||||||
|
source=self.read_string(reader),
|
||||||
|
descriptors=self.read_string(reader)
|
||||||
|
)
|
||||||
|
|
||||||
|
if message_id == 58:
|
||||||
|
return SetNodeFocus(
|
||||||
|
id=self.read_int(reader)
|
||||||
|
)
|
||||||
|
|
||||||
if message_id == 59:
|
if message_id == 59:
|
||||||
return LongTask(
|
return LongTask(
|
||||||
timestamp=self.read_uint(reader),
|
timestamp=self.read_uint(reader),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import type { Message, SetNodeScroll, CreateElementNode } from '../../messages';
|
||||||
|
|
||||||
import ListWalker from '../ListWalker';
|
import ListWalker from '../ListWalker';
|
||||||
import StylesManager, { rewriteNodeStyleSheet } from './StylesManager';
|
import StylesManager, { rewriteNodeStyleSheet } from './StylesManager';
|
||||||
|
import FocusManager from './FocusManager';
|
||||||
import {
|
import {
|
||||||
VElement,
|
VElement,
|
||||||
VText,
|
VText,
|
||||||
|
|
@ -35,9 +36,9 @@ const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/; // regexp costs ~
|
||||||
|
|
||||||
|
|
||||||
export default class DOMManager extends ListWalker<Message> {
|
export default class DOMManager extends ListWalker<Message> {
|
||||||
private vTexts: Map<number, VText> = new Map() // map vs object here?
|
private readonly vTexts: Map<number, VText> = new Map() // map vs object here?
|
||||||
private vElements: Map<number, VElement> = new Map()
|
private readonly vElements: Map<number, VElement> = new Map()
|
||||||
private vRoots: Map<number, VShadowRoot | VDocument> = new Map()
|
private readonly vRoots: Map<number, VShadowRoot | VDocument> = new Map()
|
||||||
private activeIframeRoots: Map<number, number> = new Map()
|
private activeIframeRoots: Map<number, number> = new Map()
|
||||||
private styleSheets: Map<number, CSSStyleSheet> = new Map()
|
private styleSheets: Map<number, CSSStyleSheet> = new Map()
|
||||||
private ppStyleSheets: Map<number, PostponedStyleSheet> = new Map()
|
private ppStyleSheets: Map<number, PostponedStyleSheet> = new Map()
|
||||||
|
|
@ -46,6 +47,7 @@ export default class DOMManager extends ListWalker<Message> {
|
||||||
private upperBodyId: number = -1;
|
private upperBodyId: number = -1;
|
||||||
private nodeScrollManagers: Map<number, ListWalker<SetNodeScroll>> = new Map()
|
private nodeScrollManagers: Map<number, ListWalker<SetNodeScroll>> = new Map()
|
||||||
private stylesManager: StylesManager
|
private stylesManager: StylesManager
|
||||||
|
private focusManager: FocusManager = new FocusManager(this.vElements)
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
@ -67,6 +69,10 @@ export default class DOMManager extends ListWalker<Message> {
|
||||||
scrollManager.append(m)
|
scrollManager.append(m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (m.tp === "set_node_focus") {
|
||||||
|
this.focusManager.append(m)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (m.tp === "create_element_node") {
|
if (m.tp === "create_element_node") {
|
||||||
if(m.tag === "BODY" && this.upperBodyId === -1) {
|
if(m.tag === "BODY" && this.upperBodyId === -1) {
|
||||||
this.upperBodyId = m.id
|
this.upperBodyId = m.id
|
||||||
|
|
@ -126,7 +132,7 @@ export default class DOMManager extends ListWalker<Message> {
|
||||||
parent.insertChildAt(child, index)
|
parent.insertChildAt(child, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyMessage = (msg: Message): void => {
|
private applyMessage = (msg: Message): Promise<any> | undefined => {
|
||||||
let node: Node | undefined
|
let node: Node | undefined
|
||||||
let vn: VNode | undefined
|
let vn: VNode | undefined
|
||||||
let doc: Document | null
|
let doc: Document | null
|
||||||
|
|
@ -145,12 +151,15 @@ export default class DOMManager extends ListWalker<Message> {
|
||||||
fRoot.innerText = '';
|
fRoot.innerText = '';
|
||||||
|
|
||||||
vn = new VElement(fRoot)
|
vn = new VElement(fRoot)
|
||||||
this.vElements = new Map([[0, vn]])
|
this.vElements.clear()
|
||||||
|
this.vElements.set(0, vn)
|
||||||
const vDoc = new VDocument(doc)
|
const vDoc = new VDocument(doc)
|
||||||
vDoc.insertChildAt(vn, 0)
|
vDoc.insertChildAt(vn, 0)
|
||||||
this.vRoots = new Map([[0, vDoc]]) // watchout: id==0 for both Document and documentElement
|
this.vRoots.clear()
|
||||||
|
this.vRoots.set(0, vDoc) // watchout: id==0 for both Document and documentElement
|
||||||
// this is done for the AdoptedCSS logic
|
// this is done for the AdoptedCSS logic
|
||||||
// todo: start from 0 (sync logic with tracker)
|
// todo: start from 0 (sync logic with tracker)
|
||||||
|
this.vTexts.clear()
|
||||||
this.stylesManager.reset()
|
this.stylesManager.reset()
|
||||||
this.activeIframeRoots.clear()
|
this.activeIframeRoots.clear()
|
||||||
return
|
return
|
||||||
|
|
@ -371,20 +380,37 @@ export default class DOMManager extends ListWalker<Message> {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
vn.node.adoptedStyleSheets = [...vn.node.adoptedStyleSheets].filter(s => s !== styleSheet)
|
vn.node.adoptedStyleSheets = [...vn.node.adoptedStyleSheets].filter(s => s !== styleSheet)
|
||||||
return
|
return
|
||||||
|
case "load_font_face":
|
||||||
|
vn = this.vRoots.get(msg.parentID)
|
||||||
|
if (!vn) { logger.error("Node not found", msg); return }
|
||||||
|
if (vn instanceof VShadowRoot) { logger.error(`Node ${vn} expected to be a Document`, msg); return }
|
||||||
|
let descr: Object
|
||||||
|
try {
|
||||||
|
descr = JSON.parse(msg.descriptors)
|
||||||
|
descr = typeof descr === 'object' ? descr : undefined
|
||||||
|
} catch {
|
||||||
|
logger.warn("Can't parse font-face descriptors: ", msg)
|
||||||
|
}
|
||||||
|
const ff = new FontFace(msg.family, msg.source, descr)
|
||||||
|
vn.node.fonts.add(ff)
|
||||||
|
return ff.load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveReady(t: number): Promise<void> {
|
async moveReady(t: number): Promise<void> {
|
||||||
// MBTODO (back jump optimisation):
|
// MBTODO (back jump optimisation):
|
||||||
// - store intemediate virtual dom state
|
// - store intemediate virtual dom state
|
||||||
// - cancel previous moveReady tasks (is it possible?) if new timestamp is less
|
// - cancel previous moveReady tasks (is it possible?) if new timestamp is less
|
||||||
this.moveApply(t, this.applyMessage) // This function autoresets pointer if necessary (better name?)
|
// This function autoresets pointer if necessary (better name?)
|
||||||
|
|
||||||
|
await this.moveWait(t, this.applyMessage)
|
||||||
this.vRoots.forEach(rt => rt.applyChanges()) // MBTODO (optimisation): affected set
|
this.vRoots.forEach(rt => rt.applyChanges()) // MBTODO (optimisation): affected set
|
||||||
|
|
||||||
// Thinkabout (read): css preload
|
// Thinkabout (read): css preload
|
||||||
// What if we go back before it is ready? We'll have two handlres?
|
// What if we go back before it is ready? We'll have two handlres?
|
||||||
return this.stylesManager.moveReady(t).then(() => {
|
return this.stylesManager.moveReady(t).then(() => {
|
||||||
|
// Apply focus
|
||||||
|
this.focusManager.move(t)
|
||||||
// Apply all scrolls after the styles got applied
|
// Apply all scrolls after the styles got applied
|
||||||
this.nodeScrollManagers.forEach(manager => {
|
this.nodeScrollManagers.forEach(manager => {
|
||||||
const msg = manager.moveGetLast(t)
|
const msg = manager.moveGetLast(t)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import logger from 'App/logger';
|
||||||
|
import type { SetNodeFocus } from '../../messages';
|
||||||
|
import type { VElement } from './VirtualDOM';
|
||||||
|
import ListWalker from '../ListWalker';
|
||||||
|
|
||||||
|
|
||||||
|
const FOCUS_CLASS = "-openreplay-focus"
|
||||||
|
|
||||||
|
export default class FocusManager extends ListWalker<SetNodeFocus> {
|
||||||
|
constructor(private readonly vElements: Map<number, VElement>) {super()}
|
||||||
|
private focused: Element | null = null
|
||||||
|
move(t: number) {
|
||||||
|
const msg = this.moveGetLast(t)
|
||||||
|
if (!msg) {return}
|
||||||
|
this.focused?.classList.remove(FOCUS_CLASS)
|
||||||
|
if (msg.id === -1) {
|
||||||
|
this.focused = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const vn = this.vElements.get(msg.id)
|
||||||
|
if (!vn) { logger.error("Node not found", msg); return }
|
||||||
|
this.focused = vn.node
|
||||||
|
this.focused.classList.add(FOCUS_CLASS)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ export default class ListWalker<T extends Timed> {
|
||||||
console.error("Trying to append message with the less time then the list tail: ", m)
|
console.error("Trying to append message with the less time then the list tail: ", m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this._list.push(m);
|
this.list.push(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
|
|
@ -18,53 +18,53 @@ export default class ListWalker<T extends Timed> {
|
||||||
|
|
||||||
sort(comparator: (a: T, b: T) => number): void {
|
sort(comparator: (a: T, b: T) => number): void {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this._list.sort((m1,m2) => comparator(m1,m2) || (m1._index - m2._index) ); // indexes for sort stability (TODO: fix types???)
|
this.list.sort((m1,m2) => comparator(m1,m2) || (m1._index - m2._index) ); // indexes for sort stability (TODO: fix types???)
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach(f: (item: T) => void):void {
|
forEach(f: (item: T) => void):void {
|
||||||
this._list.forEach(f);
|
this.list.forEach(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
get last(): T | null {
|
get last(): T | null {
|
||||||
if (this._list.length === 0) {
|
if (this.list.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this._list[ this._list.length - 1 ];
|
return this.list[ this.list.length - 1 ];
|
||||||
}
|
}
|
||||||
|
|
||||||
get current(): T | null {
|
get current(): T | null {
|
||||||
if (this.p === 0) {
|
if (this.p === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this._list[ this.p - 1 ];
|
return this.list[ this.p - 1 ];
|
||||||
}
|
}
|
||||||
|
|
||||||
get timeNow(): number {
|
get timeNow(): number {
|
||||||
if (this.p === 0) {
|
if (this.p === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return this._list[ this.p - 1 ].time;
|
return this.list[ this.p - 1 ].time;
|
||||||
}
|
}
|
||||||
|
|
||||||
get length(): number {
|
get length(): number {
|
||||||
return this._list.length;
|
return this.list.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get maxTime(): number {
|
get maxTime(): number {
|
||||||
if (this.length === 0) {
|
if (this.length === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return this._list[ this.length - 1 ].time;
|
return this.list[ this.length - 1 ].time;
|
||||||
}
|
}
|
||||||
get minTime(): number {
|
get minTime(): number {
|
||||||
if (this.length === 0) {
|
if (this.length === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return this._list[ 0 ].time;
|
return this.list[ 0 ].time;
|
||||||
}
|
}
|
||||||
|
|
||||||
get listNow(): Array<T> {
|
get listNow(): Array<T> {
|
||||||
return this._list.slice(0, this.p);
|
return this.list.slice(0, this.p);
|
||||||
}
|
}
|
||||||
|
|
||||||
get list(): Array<T> {
|
get list(): Array<T> {
|
||||||
|
|
@ -93,28 +93,42 @@ export default class ListWalker<T extends Timed> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let changed = false;
|
let changed = false;
|
||||||
while (this.p < this.length && this._list[this.p][key] <= val) {
|
while (this.p < this.length && this.list[this.p][key] <= val) {
|
||||||
this.p++;
|
this.p++;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
while (this.p > 0 && this._list[ this.p - 1 ][key] > val) {
|
while (this.p > 0 && this.list[ this.p - 1 ][key] > val) {
|
||||||
this.p--;
|
this.p--;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
return changed ? this._list[ this.p - 1 ] : null;
|
return changed ? this.list[ this.p - 1 ] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveApply(t: number, fn: (T) => void, fnBack?: (T) => void): void {
|
moveApply(t: number, fn: (msg: T) => void, fnBack?: (msg: T) => void): void {
|
||||||
// Applying only in increment order for now
|
// Applying only in increment order for now
|
||||||
if (t < this.timeNow) {
|
if (t < this.timeNow) {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!!this._list[this.p] && this._list[this.p].time <= t) {
|
const list = this.list
|
||||||
fn(this._list[ this.p++ ]);
|
while (list[this.p] && list[this.p].time <= t) {
|
||||||
|
fn(list[ this.p++ ]);
|
||||||
}
|
}
|
||||||
while (fnBack && this.p > 0 && this._list[ this.p - 1 ].time > t) {
|
while (fnBack && this.p > 0 && list[ this.p - 1 ].time > t) {
|
||||||
fnBack(this._list[ --this.p ]);
|
fnBack(list[ --this.p ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async moveWait(t: number, fn: (msg: T) => Promise<any> | undefined): Promise<void> {
|
||||||
|
// Applying only in increment order for now
|
||||||
|
if (t < this.timeNow) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = this.list
|
||||||
|
while (list[this.p] && list[this.p].time <= t) {
|
||||||
|
const ret = fn(this.list[ this.p++ ]);
|
||||||
|
if (ret) { await ret }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,28 @@ export default class RawMessageReader extends PrimitiveReader {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 57: {
|
||||||
|
const parentID = this.readUint(); if (parentID === null) { return resetPointer() }
|
||||||
|
const family = this.readString(); if (family === null) { return resetPointer() }
|
||||||
|
const source = this.readString(); if (source === null) { return resetPointer() }
|
||||||
|
const descriptors = this.readString(); if (descriptors === null) { return resetPointer() }
|
||||||
|
return {
|
||||||
|
tp: "load_font_face",
|
||||||
|
parentID,
|
||||||
|
family,
|
||||||
|
source,
|
||||||
|
descriptors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 58: {
|
||||||
|
const id = this.readInt(); if (id === null) { return resetPointer() }
|
||||||
|
return {
|
||||||
|
tp: "set_node_focus",
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 59: {
|
case 59: {
|
||||||
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
|
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
|
||||||
const duration = this.readUint(); if (duration === null) { return resetPointer() }
|
const duration = this.readUint(); if (duration === null) { return resetPointer() }
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ import type {
|
||||||
RawPerformanceTrack,
|
RawPerformanceTrack,
|
||||||
RawConnectionInformation,
|
RawConnectionInformation,
|
||||||
RawSetPageVisibility,
|
RawSetPageVisibility,
|
||||||
|
RawLoadFontFace,
|
||||||
|
RawSetNodeFocus,
|
||||||
RawLongTask,
|
RawLongTask,
|
||||||
RawSetNodeAttributeURLBased,
|
RawSetNodeAttributeURLBased,
|
||||||
RawSetCssDataURLBased,
|
RawSetCssDataURLBased,
|
||||||
|
|
@ -123,6 +125,10 @@ export type ConnectionInformation = RawConnectionInformation & Timed
|
||||||
|
|
||||||
export type SetPageVisibility = RawSetPageVisibility & Timed
|
export type SetPageVisibility = RawSetPageVisibility & Timed
|
||||||
|
|
||||||
|
export type LoadFontFace = RawLoadFontFace & Timed
|
||||||
|
|
||||||
|
export type SetNodeFocus = RawSetNodeFocus & Timed
|
||||||
|
|
||||||
export type LongTask = RawLongTask & Timed
|
export type LongTask = RawLongTask & Timed
|
||||||
|
|
||||||
export type SetNodeAttributeURLBased = RawSetNodeAttributeURLBased & Timed
|
export type SetNodeAttributeURLBased = RawSetNodeAttributeURLBased & Timed
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,19 @@ export interface RawSetPageVisibility {
|
||||||
hidden: boolean,
|
hidden: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RawLoadFontFace {
|
||||||
|
tp: "load_font_face",
|
||||||
|
parentID: number,
|
||||||
|
family: string,
|
||||||
|
source: string,
|
||||||
|
descriptors: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RawSetNodeFocus {
|
||||||
|
tp: "set_node_focus",
|
||||||
|
id: number,
|
||||||
|
}
|
||||||
|
|
||||||
export interface RawLongTask {
|
export interface RawLongTask {
|
||||||
tp: "long_task",
|
tp: "long_task",
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
|
|
@ -378,4 +391,4 @@ export interface RawIosNetworkCall {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawConnectionInformation | RawSetPageVisibility | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall;
|
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall;
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ export const TP_MAP = {
|
||||||
53: "resource_timing",
|
53: "resource_timing",
|
||||||
54: "connection_information",
|
54: "connection_information",
|
||||||
55: "set_page_visibility",
|
55: "set_page_visibility",
|
||||||
|
57: "load_font_face",
|
||||||
|
58: "set_node_focus",
|
||||||
59: "long_task",
|
59: "long_task",
|
||||||
60: "set_node_attribute_url_based",
|
60: "set_node_attribute_url_based",
|
||||||
61: "set_css_data_url_based",
|
61: "set_css_data_url_based",
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,19 @@ type TrSetPageVisibility = [
|
||||||
hidden: boolean,
|
hidden: boolean,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
type TrLoadFontFace = [
|
||||||
|
type: 57,
|
||||||
|
parentID: number,
|
||||||
|
family: string,
|
||||||
|
source: string,
|
||||||
|
descriptors: string,
|
||||||
|
]
|
||||||
|
|
||||||
|
type TrSetNodeFocus = [
|
||||||
|
type: 58,
|
||||||
|
id: number,
|
||||||
|
]
|
||||||
|
|
||||||
type TrLongTask = [
|
type TrLongTask = [
|
||||||
type: 59,
|
type: 59,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
|
|
@ -398,7 +411,7 @@ type TrJSException = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
export type TrackerMessage = TrBatchMetadata | TrPartitionedMessage | TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrJSExceptionDeprecated | TrRawCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrZustand | TrJSException
|
export type TrackerMessage = TrBatchMetadata | TrPartitionedMessage | TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrJSExceptionDeprecated | TrRawCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrZustand | TrJSException
|
||||||
|
|
||||||
export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
||||||
switch(tMsg[0]) {
|
switch(tMsg[0]) {
|
||||||
|
|
@ -662,6 +675,23 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 57: {
|
||||||
|
return {
|
||||||
|
tp: "load_font_face",
|
||||||
|
parentID: tMsg[1],
|
||||||
|
family: tMsg[2],
|
||||||
|
source: tMsg[3],
|
||||||
|
descriptors: tMsg[4],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 58: {
|
||||||
|
return {
|
||||||
|
tp: "set_node_focus",
|
||||||
|
id: tMsg[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case 59: {
|
case 59: {
|
||||||
return {
|
return {
|
||||||
tp: "long_task",
|
tp: "long_task",
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,14 @@ function rewriteCSSLinks(css: string, rewriter: (rawurl: string) => string): str
|
||||||
return css
|
return css
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rewritePseudoclasses(css: string): string {
|
||||||
|
return css
|
||||||
|
.replace(/:hover/g, ".-openreplay-hover")
|
||||||
|
.replace(/:focus/g, ".-openreplay-focus")
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveCSS(baseURL: string, css: string): string {
|
export function resolveCSS(baseURL: string, css: string): string {
|
||||||
return rewriteCSSLinks(css, rawurl => resolveURL(baseURL, rawurl));
|
return rewritePseudoclasses(
|
||||||
|
rewriteCSSLinks(css, rawurl => resolveURL(baseURL, rawurl))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4,11 +4,5 @@
|
||||||
To generate all necessary files for the project:
|
To generate all necessary files for the project:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ruby run.rb
|
sh generate.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
In order format generated files run:
|
|
||||||
```sh
|
|
||||||
sh format.sh
|
|
||||||
```
|
|
||||||
(Otherwise there will be changes in stage)
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
|
ruby run.rb
|
||||||
gofmt -w ../backend/pkg/messages
|
gofmt -w ../backend/pkg/messages
|
||||||
|
|
@ -359,7 +359,19 @@ message 56, 'PerformanceTrackAggr', :tracker => false, :replayer => false do
|
||||||
uint 'AvgUsedJSHeapSize'
|
uint 'AvgUsedJSHeapSize'
|
||||||
uint 'MaxUsedJSHeapSize'
|
uint 'MaxUsedJSHeapSize'
|
||||||
end
|
end
|
||||||
## 57 58
|
|
||||||
|
# Since 4.1.7 / 1.9.0
|
||||||
|
message 57, 'LoadFontFace' do
|
||||||
|
uint 'ParentID'
|
||||||
|
string 'Family'
|
||||||
|
string 'Source'
|
||||||
|
string 'Descriptors'
|
||||||
|
end
|
||||||
|
# Since 4.1.7 / 1.9.0
|
||||||
|
message 58, 'SetNodeFocus' do
|
||||||
|
int 'ID'
|
||||||
|
end
|
||||||
|
|
||||||
#Depricated (since 3.0.?)
|
#Depricated (since 3.0.?)
|
||||||
message 59, 'LongTask' do
|
message 59, 'LongTask' do
|
||||||
uint 'Timestamp'
|
uint 'Timestamp'
|
||||||
|
|
@ -466,6 +478,8 @@ message 78, 'JSException', :replayer => false do
|
||||||
string 'Payload'
|
string 'Payload'
|
||||||
string 'Metadata'
|
string 'Metadata'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
message 126, 'SessionEnd', :tracker => false, :replayer => false do
|
message 126, 'SessionEnd', :tracker => false, :replayer => false do
|
||||||
uint 'Timestamp'
|
uint 'Timestamp'
|
||||||
string 'EncryptionKey'
|
string 'EncryptionKey'
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ export declare const enum Type {
|
||||||
ResourceTiming = 53,
|
ResourceTiming = 53,
|
||||||
ConnectionInformation = 54,
|
ConnectionInformation = 54,
|
||||||
SetPageVisibility = 55,
|
SetPageVisibility = 55,
|
||||||
|
LoadFontFace = 57,
|
||||||
|
SetNodeFocus = 58,
|
||||||
LongTask = 59,
|
LongTask = 59,
|
||||||
SetNodeAttributeURLBased = 60,
|
SetNodeAttributeURLBased = 60,
|
||||||
SetCSSDataURLBased = 61,
|
SetCSSDataURLBased = 61,
|
||||||
|
|
@ -348,6 +350,19 @@ export type SetPageVisibility = [
|
||||||
/*hidden:*/ boolean,
|
/*hidden:*/ boolean,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export type LoadFontFace = [
|
||||||
|
/*type:*/ Type.LoadFontFace,
|
||||||
|
/*parentID:*/ number,
|
||||||
|
/*family:*/ string,
|
||||||
|
/*source:*/ string,
|
||||||
|
/*descriptors:*/ string,
|
||||||
|
]
|
||||||
|
|
||||||
|
export type SetNodeFocus = [
|
||||||
|
/*type:*/ Type.SetNodeFocus,
|
||||||
|
/*id:*/ number,
|
||||||
|
]
|
||||||
|
|
||||||
export type LongTask = [
|
export type LongTask = [
|
||||||
/*type:*/ Type.LongTask,
|
/*type:*/ Type.LongTask,
|
||||||
/*timestamp:*/ number,
|
/*timestamp:*/ number,
|
||||||
|
|
@ -456,5 +471,5 @@ export type JSException = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
type Message = BatchMetadata | PartitionedMessage | Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | PageLoadTiming | PageRenderTiming | JSExceptionDeprecated | RawCustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | ResourceTiming | ConnectionInformation | SetPageVisibility | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | Zustand | JSException
|
type Message = BatchMetadata | PartitionedMessage | Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | PageLoadTiming | PageRenderTiming | JSExceptionDeprecated | RawCustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | ResourceTiming | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | Zustand | JSException
|
||||||
export default Message
|
export default Message
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ export function isRootNode(node: Node): node is Document | DocumentFragment {
|
||||||
|
|
||||||
type TagTypeMap = {
|
type TagTypeMap = {
|
||||||
HTML: HTMLHtmlElement
|
HTML: HTMLHtmlElement
|
||||||
|
BODY: HTMLBodyElement
|
||||||
IMG: HTMLImageElement
|
IMG: HTMLImageElement
|
||||||
INPUT: HTMLInputElement
|
INPUT: HTMLInputElement
|
||||||
TEXTAREA: HTMLTextAreaElement
|
TEXTAREA: HTMLTextAreaElement
|
||||||
|
|
|
||||||
|
|
@ -480,7 +480,8 @@ export default class App {
|
||||||
|
|
||||||
const onStartInfo = { sessionToken: token, userUUID, sessionID }
|
const onStartInfo = { sessionToken: token, userUUID, sessionID }
|
||||||
|
|
||||||
this.startCallbacks.forEach((cb) => cb(onStartInfo)) // TODO: start as early as possible (before receiving the token)
|
// TODO: start as early as possible (before receiving the token)
|
||||||
|
this.startCallbacks.forEach((cb) => cb(onStartInfo)) // MBTODO: callbacks after DOM "mounted" (observed)
|
||||||
this.observer.observe()
|
this.observer.observe()
|
||||||
this.ticker.start()
|
this.ticker.start()
|
||||||
this.activityState = ActivityState.Active
|
this.activityState = ActivityState.Active
|
||||||
|
|
|
||||||
|
|
@ -535,6 +535,30 @@ export function SetPageVisibility(
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function LoadFontFace(
|
||||||
|
parentID: number,
|
||||||
|
family: string,
|
||||||
|
source: string,
|
||||||
|
descriptors: string,
|
||||||
|
): Messages.LoadFontFace {
|
||||||
|
return [
|
||||||
|
Messages.Type.LoadFontFace,
|
||||||
|
parentID,
|
||||||
|
family,
|
||||||
|
source,
|
||||||
|
descriptors,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SetNodeFocus(
|
||||||
|
id: number,
|
||||||
|
): Messages.SetNodeFocus {
|
||||||
|
return [
|
||||||
|
Messages.Type.SetNodeFocus,
|
||||||
|
id,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
export function LongTask(
|
export function LongTask(
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
duration: number,
|
duration: number,
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,8 @@ export default class TopObserver extends Observer {
|
||||||
window.document,
|
window.document,
|
||||||
() => {
|
() => {
|
||||||
this.app.send(CreateDocument())
|
this.app.send(CreateDocument())
|
||||||
|
// it has no node_id here
|
||||||
|
this.app.nodes.callNodeCallbacks(document, true)
|
||||||
},
|
},
|
||||||
window.document.documentElement,
|
window.document.documentElement,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import Performance from './modules/performance.js'
|
||||||
import Scroll from './modules/scroll.js'
|
import Scroll from './modules/scroll.js'
|
||||||
import Viewport from './modules/viewport.js'
|
import Viewport from './modules/viewport.js'
|
||||||
import CSSRules from './modules/cssrules.js'
|
import CSSRules from './modules/cssrules.js'
|
||||||
|
import Focus from './modules/focus.js'
|
||||||
|
import Fonts from './modules/fonts.js'
|
||||||
import ConstructedStyleSheets from './modules/constructedStyleSheets.js'
|
import ConstructedStyleSheets from './modules/constructedStyleSheets.js'
|
||||||
import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js'
|
import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js'
|
||||||
|
|
||||||
|
|
@ -123,6 +125,8 @@ export default class API {
|
||||||
Timing(app, options)
|
Timing(app, options)
|
||||||
Performance(app, options)
|
Performance(app, options)
|
||||||
Scroll(app)
|
Scroll(app)
|
||||||
|
Focus(app)
|
||||||
|
Fonts(app)
|
||||||
;(window as any).__OPENREPLAY__ = this
|
;(window as any).__OPENREPLAY__ = this
|
||||||
|
|
||||||
if (options.autoResetOnWindowOpen) {
|
if (options.autoResetOnWindowOpen) {
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,6 @@ export default function (app: App | null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!hasAdoptedSS(document)) {
|
if (!hasAdoptedSS(document)) {
|
||||||
app.attachStartCallback(() => {
|
|
||||||
// MBTODO: pre-start sendQueue app
|
|
||||||
app.send(TechnicalInfo('no_adopted_stylesheets', ''))
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
45
tracker/tracker/src/main/modules/focus.ts
Normal file
45
tracker/tracker/src/main/modules/focus.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import type App from '../app/index.js'
|
||||||
|
import { isNode, hasTag } from '../app/guards.js'
|
||||||
|
import { SetNodeFocus } from '../app/messages.gen.js'
|
||||||
|
|
||||||
|
export default function (app: App): void {
|
||||||
|
function sendSetNodeFocus(n: Node) {
|
||||||
|
const id = app.nodes.getID(n)
|
||||||
|
if (id !== undefined) {
|
||||||
|
app.send(SetNodeFocus(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let blurred = false
|
||||||
|
app.nodes.attachNodeCallback((node) => {
|
||||||
|
if (!hasTag(node, 'BODY')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.nodes.attachNodeListener(node, 'focus', (e: FocusEvent): void => {
|
||||||
|
if (!isNode(e.target)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendSetNodeFocus(e.target)
|
||||||
|
blurred = false
|
||||||
|
})
|
||||||
|
app.nodes.attachNodeListener(node, 'blur', (e: FocusEvent): void => {
|
||||||
|
if (e.relatedTarget === null) {
|
||||||
|
blurred = true
|
||||||
|
setTimeout(() => {
|
||||||
|
if (blurred) {
|
||||||
|
app.send(SetNodeFocus(-1))
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
app.attachStartCallback(() => {
|
||||||
|
let elem = document.activeElement
|
||||||
|
while (elem && hasTag(elem, 'IFRAME') && elem.contentDocument) {
|
||||||
|
elem = elem.contentDocument.activeElement
|
||||||
|
}
|
||||||
|
if (elem && elem !== elem.ownerDocument.body) {
|
||||||
|
sendSetNodeFocus(elem)
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
66
tracker/tracker/src/main/modules/fonts.ts
Normal file
66
tracker/tracker/src/main/modules/fonts.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import type App from '../app/index.js'
|
||||||
|
import { isDocument } from '../app/guards.js'
|
||||||
|
import { LoadFontFace } from '../app/messages.gen.js'
|
||||||
|
|
||||||
|
type FFData = [string, string, string]
|
||||||
|
|
||||||
|
export default function (app: App) {
|
||||||
|
if (!window.FontFace) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const docFonts: Map<Document, FFData[]> = new Map()
|
||||||
|
|
||||||
|
const patchWindow = (wnd: typeof globalThis) => {
|
||||||
|
class FontFaceInterceptor extends wnd.FontFace {
|
||||||
|
constructor(...args: ConstructorParameters<typeof FontFace>) {
|
||||||
|
//maybe do this on load(). In this case check if the document.fonts.load(...) function calls the font's load()
|
||||||
|
if (typeof args[1] === 'string') {
|
||||||
|
let desc = ''
|
||||||
|
if (args[2]) {
|
||||||
|
app.safe(() => {
|
||||||
|
desc = JSON.stringify(args[2])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ffData: FFData = [args[0], args[1], desc]
|
||||||
|
const ffDataArr = docFonts.get(wnd.document) || []
|
||||||
|
ffDataArr.push(ffData)
|
||||||
|
docFonts.set(wnd.document, ffDataArr)
|
||||||
|
|
||||||
|
const parentID = wnd === window ? 0 : app.nodes.getID(wnd.document)
|
||||||
|
if (parentID === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.active()) {
|
||||||
|
app.send(LoadFontFace(parentID, ...ffData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wnd.FontFace = FontFaceInterceptor
|
||||||
|
}
|
||||||
|
app.observer.attachContextCallback(patchWindow)
|
||||||
|
patchWindow(window)
|
||||||
|
|
||||||
|
app.nodes.attachNodeCallback((node) => {
|
||||||
|
if (!isDocument(node)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ffDataArr = docFonts.get(node)
|
||||||
|
if (!ffDataArr) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentID = node.defaultView === window ? 0 : app.nodes.getID(node)
|
||||||
|
if (parentID === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ffDataArr.forEach((ffData) => {
|
||||||
|
app.send(LoadFontFace(parentID, ...ffData))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -178,6 +178,14 @@ export default class MessageEncoder extends PrimitiveEncoder {
|
||||||
return this.boolean(msg[1])
|
return this.boolean(msg[1])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case Messages.Type.LoadFontFace:
|
||||||
|
return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4])
|
||||||
|
break
|
||||||
|
|
||||||
|
case Messages.Type.SetNodeFocus:
|
||||||
|
return this.int(msg[1])
|
||||||
|
break
|
||||||
|
|
||||||
case Messages.Type.LongTask:
|
case Messages.Type.LongTask:
|
||||||
return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.string(msg[5]) && this.string(msg[6]) && this.string(msg[7])
|
return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.string(msg[5]) && this.string(msg[6]) && this.string(msg[7])
|
||||||
break
|
break
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue