fix(tracker): capture selection event

This commit is contained in:
nick-delirium 2023-02-10 12:41:01 +01:00
parent c57846eab3
commit c2501530a0
17 changed files with 176 additions and 5 deletions

View file

@ -10,5 +10,5 @@ func IsIOSType(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 || 50 == id || 51 == 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
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 || 50 == id || 51 == 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 || 84 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id
}

View file

@ -80,6 +80,7 @@ const (
MsgBatchMetadata = 81
MsgPartitionedMessage = 82
MsgInputChange = 83
MsgSelectionChange = 84
MsgIssueEvent = 125
MsgSessionEnd = 126
MsgSessionSearch = 127
@ -2143,6 +2144,31 @@ func (msg *InputChange) TypeID() int {
return 83
}
type SelectionChange struct {
message
SelectionStart uint64
SelectionEnd uint64
Selection string
}
func (msg *SelectionChange) Encode() []byte {
buf := make([]byte, 31+len(msg.Selection))
buf[0] = 84
p := 1
p = WriteUint(msg.SelectionStart, buf, p)
p = WriteUint(msg.SelectionEnd, buf, p)
p = WriteString(msg.Selection, buf, p)
return buf[:p]
}
func (msg *SelectionChange) Decode() Message {
return msg
}
func (msg *SelectionChange) TypeID() int {
return 84
}
type IssueEvent struct {
message
MessageID uint64

View file

@ -1308,6 +1308,21 @@ func DecodeInputChange(reader BytesReader) (Message, error) {
return msg, err
}
func DecodeSelectionChange(reader BytesReader) (Message, error) {
var err error = nil
msg := &SelectionChange{}
if msg.SelectionStart, err = reader.ReadUint(); err != nil {
return nil, err
}
if msg.SelectionEnd, err = reader.ReadUint(); err != nil {
return nil, err
}
if msg.Selection, err = reader.ReadString(); err != nil {
return nil, err
}
return msg, err
}
func DecodeIssueEvent(reader BytesReader) (Message, error) {
var err error = nil
msg := &IssueEvent{}
@ -1919,6 +1934,8 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
return DecodePartitionedMessage(reader)
case 83:
return DecodeInputChange(reader)
case 84:
return DecodeSelectionChange(reader)
case 125:
return DecodeIssueEvent(reader)
case 126:

View file

@ -753,6 +753,15 @@ class InputChange(Message):
self.hesitation_time = hesitation_time
class SelectionChange(Message):
__id__ = 84
def __init__(self, selection_start, selection_end, selection):
self.selection_start = selection_start
self.selection_end = selection_end
self.selection = selection
class IssueEvent(Message):
__id__ = 125

View file

@ -667,6 +667,13 @@ class MessageCodec(Codec):
hesitation_time=self.read_int(reader)
)
if message_id == 84:
return SelectionChange(
selection_start=self.read_uint(reader),
selection_end=self.read_uint(reader),
selection=self.read_string(reader)
)
if message_id == 125:
return IssueEvent(
message_id=self.read_uint(reader),

View file

@ -627,6 +627,18 @@ export default class RawMessageReader extends PrimitiveReader {
};
}
case 84: {
const selectionStart = this.readUint(); if (selectionStart === null) { return resetPointer() }
const selectionEnd = this.readUint(); if (selectionEnd === null) { return resetPointer() }
const selection = this.readString(); if (selection === null) { return resetPointer() }
return {
tp: MType.SelectionChange,
selectionStart,
selectionEnd,
selection,
};
}
case 90: {
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
const projectID = this.readUint(); if (projectID === null) { return resetPointer() }

View file

@ -3,7 +3,7 @@
import { MType } from './raw.gen'
const DOM_TYPES = [0,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,37,38,49,50,51,54,55,57,58,59,60,61,67,69,70,71,72,73,74,75,76,77,90,93,96,100,102,103,105]
const DOM_TYPES = [0,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,37,38,49,50,51,54,55,57,58,59,60,61,67,69,70,71,72,73,74,75,76,77,84,90,93,96,100,102,103,105]
export function isDOMType(t: MType) {
return DOM_TYPES.includes(t)
}

View file

@ -55,6 +55,7 @@ import type {
RawAdoptedSsAddOwner,
RawAdoptedSsRemoveOwner,
RawZustand,
RawSelectionChange,
RawIosSessionStart,
RawIosCustomEvent,
RawIosScreenChanges,
@ -169,6 +170,8 @@ export type AdoptedSsRemoveOwner = RawAdoptedSsRemoveOwner & Timed
export type Zustand = RawZustand & Timed
export type SelectionChange = RawSelectionChange & Timed
export type IosSessionStart = RawIosSessionStart & Timed
export type IosCustomEvent = RawIosCustomEvent & Timed

View file

@ -53,6 +53,7 @@ export const enum MType {
AdoptedSsAddOwner = 76,
AdoptedSsRemoveOwner = 77,
Zustand = 79,
SelectionChange = 84,
IosSessionStart = 90,
IosCustomEvent = 93,
IosScreenChanges = 96,
@ -418,6 +419,13 @@ export interface RawZustand {
state: string,
}
export interface RawSelectionChange {
tp: MType.SelectionChange,
selectionStart: number,
selectionEnd: number,
selection: string,
}
export interface RawIosSessionStart {
tp: MType.IosSessionStart,
timestamp: number,
@ -489,4 +497,4 @@ export interface RawIosNetworkCall {
}
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequest | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTiming | 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;
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequest | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTiming | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawSelectionChange | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall;

View file

@ -54,6 +54,7 @@ export const TP_MAP = {
76: MType.AdoptedSsAddOwner,
77: MType.AdoptedSsRemoveOwner,
79: MType.Zustand,
84: MType.SelectionChange,
90: MType.IosSessionStart,
93: MType.IosCustomEvent,
96: MType.IosScreenChanges,

View file

@ -436,8 +436,15 @@ type TrInputChange = [
hesitationTime: number,
]
type TrSelectionChange = [
type: 84,
selectionStart: number,
selectionEnd: number,
selection: string,
]
export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequest | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrInputChange
export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequest | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrInputChange | TrSelectionChange
export default function translate(tMsg: TrackerMessage): RawMessage | null {
switch(tMsg[0]) {
@ -874,6 +881,15 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
}
}
case 84: {
return {
tp: MType.SelectionChange,
selectionStart: tMsg[1],
selectionEnd: tMsg[2],
selection: tMsg[3],
}
}
default:
return null
}

View file

@ -481,6 +481,12 @@ message 83, 'InputChange', :replayer => false do
int 'HesitationTime'
end
message 84, 'SelectionChange' do
uint 'SelectionStart'
uint 'SelectionEnd'
string 'Selection'
end
## Backend-only
message 125, 'IssueEvent', :replayer => false, :tracker => false do
uint 'MessageID'

View file

@ -64,6 +64,7 @@ export declare const enum Type {
BatchMetadata = 81,
PartitionedMessage = 82,
InputChange = 83,
SelectionChange = 84,
}
@ -498,6 +499,13 @@ export type InputChange = [
/*hesitationTime:*/ number,
]
export type SelectionChange = [
/*type:*/ Type.SelectionChange,
/*selectionStart:*/ number,
/*selectionEnd:*/ number,
/*selection:*/ string,
]
type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTiming | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | InputChange
type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTiming | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | InputChange | SelectionChange
export default Message

View file

@ -805,3 +805,16 @@ export function InputChange(
]
}
export function SelectionChange(
selectionStart: number,
selectionEnd: number,
selection: string,
): Messages.SelectionChange {
return [
Messages.Type.SelectionChange,
selectionStart,
selectionEnd,
selection,
]
}

View file

@ -24,6 +24,7 @@ import Focus from './modules/focus.js'
import Fonts from './modules/fonts.js'
import Network from './modules/network.js'
import ConstructedStyleSheets from './modules/constructedStyleSheets.js'
import Selection from './modules/selection.js'
import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js'
import type { Options as AppOptions } from './app/index.js'
@ -131,6 +132,7 @@ export default class API {
Focus(app)
Fonts(app)
Network(app, options.network)
Selection(app)
;(window as any).__OPENREPLAY__ = this
if (options.autoResetOnWindowOpen) {

View file

@ -0,0 +1,39 @@
import type App from '../app/index.js'
import { SelectionChange } from '../app/messages.gen.js'
function selection(app: App) {
app.attachEventListener(document, 'selectionchange', () => {
const selection = document.getSelection()
if (selection !== null && !selection.isCollapsed) {
const selectionStart = app.nodes.getID(selection.anchorNode!)
const selectionEnd = app.nodes.getID(selection.focusNode!)
const selectedText = selection.toString().replace(/\s+/g, ' ')
if (selectionStart && selectionEnd) {
app.send(SelectionChange(selectionStart, selectionEnd, selectedText))
}
} else {
app.send(SelectionChange(0, 0, ''))
}
})
}
export default selection
/** TODO: research how to get all in-between nodes inside selection range
* including nodes between anchor and focus nodes and their children
* without recursively searching the dom tree
*/
// if (selection.rangeCount) {
// const nodes = [];
// for (let i = 0; i < selection.rangeCount; i++) {
// const range = selection.getRangeAt(i);
// let node: Node | null = range.startContainer;
// while (node) {
// nodes.push(node);
// if (node === range.endContainer) break;
// node = node.nextSibling;
// }
// }
// // send selected nodes
// }

View file

@ -258,6 +258,10 @@ export default class MessageEncoder extends PrimitiveEncoder {
return this.uint(msg[1]) && this.string(msg[2]) && this.int(msg[3])
break
case Messages.Type.SelectionChange:
return this.uint(msg[1]) && this.uint(msg[2]) && this.string(msg[3])
break
}
}