-
Notifications
You must be signed in to change notification settings - Fork 170
Expand file tree
/
Copy pathserializationUtils.ts
More file actions
103 lines (88 loc) · 3.6 KB
/
serializationUtils.ts
File metadata and controls
103 lines (88 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import { buildUrl } from '@datadog/browser-core'
import { CENSORED_STRING_MARK, shouldMaskNode } from '@datadog/browser-rum-core'
import type { NodePrivacyLevel } from '@datadog/browser-rum-core'
/**
* Get the element "value" to be serialized as an attribute or an input update record. It respects
* the input privacy mode of the element.
* PERFROMANCE OPTIMIZATION: Assumes that privacy level `HIDDEN` is never encountered because of earlier checks.
*/
export function getElementInputValue(element: Element, nodePrivacyLevel: NodePrivacyLevel) {
/*
BROWSER SPEC NOTE: <input>, <select>
For some <input> elements, the `value` is an exceptional property/attribute that has the
value synced between el.value and el.getAttribute()
input[type=button,checkbox,hidden,image,radio,reset,submit]
*/
const tagName = element.tagName
const value = (element as HTMLInputElement | HTMLTextAreaElement).value
if (shouldMaskNode(element, nodePrivacyLevel)) {
const type = (element as HTMLInputElement | HTMLTextAreaElement).type
if (tagName === 'INPUT' && (type === 'button' || type === 'submit' || type === 'reset')) {
// Overrule `MASK` privacy level for button-like element values, as they are used during replay
// to display their label. They can still be hidden via the "hidden" privacy attribute or class name.
return value
} else if (!value || tagName === 'OPTION') {
// <Option> value provides no benefit
return
}
return CENSORED_STRING_MARK
}
if (tagName === 'OPTION' || tagName === 'SELECT') {
return (element as HTMLOptionElement | HTMLSelectElement).value
}
if (tagName !== 'INPUT' && tagName !== 'TEXTAREA') {
return
}
return value
}
export const URL_IN_CSS_REF = /url\((?:(')([^']*)'|(")([^"]*)"|([^)]*))\)/gm
export const ABSOLUTE_URL = /^[A-Za-z]+:|^\/\//
export const DATA_URI = /^["']?data:.*,/i
export function switchToAbsoluteUrl(cssText: string, cssHref: string | null): string {
return cssText.replace(
URL_IN_CSS_REF,
(
matchingSubstring: string,
singleQuote: string | undefined,
urlWrappedInSingleQuotes: string | undefined,
doubleQuote: string | undefined,
urlWrappedInDoubleQuotes: string | undefined,
urlNotWrappedInQuotes: string | undefined
) => {
const url = urlWrappedInSingleQuotes || urlWrappedInDoubleQuotes || urlNotWrappedInQuotes
if (!cssHref || !url || ABSOLUTE_URL.test(url) || DATA_URI.test(url)) {
return matchingSubstring
}
const quote = singleQuote || doubleQuote || ''
return `url(${quote}${makeUrlAbsolute(url, cssHref)}${quote})`
}
)
}
function makeUrlAbsolute(url: string, baseUrl: string): string {
try {
return buildUrl(url, baseUrl).href
} catch {
return url
}
}
const TAG_NAME_REGEX = /[^a-z1-6-_]/
export function getValidTagName(tagName: string): string {
const processedTagName = tagName.toLowerCase().trim()
if (TAG_NAME_REGEX.test(processedTagName)) {
// if the tag name is odd and we cannot extract
// anything from the string, then we return a
// generic div
return 'div'
}
return processedTagName
}
/**
* Returns the tag name of the given element, normalized to ensure a consistent lowercase
* representation regardless of whether the element is HTML, XHTML, or SVG.
*/
export function normalizedTagName(element: Element): string {
return element.tagName.toLowerCase()
}
export function censoredImageForSize(width: number, height: number) {
return `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}' style='background-color:silver'%3E%3C/svg%3E`
}