11import { ExperimentalFeature , isExperimentalFeatureEnabled , safeTruncate } from '@datadog/browser-core'
22import { getPrivacySelector , NodePrivacyLevel } from '../privacyConstants'
3- import { getNodePrivacyLevel , maskDisallowedTextContent , shouldMaskNode } from '../privacy'
3+ import { getNodePrivacyLevel , maskDisallowedTextContent , shouldMaskNode , shouldMaskAttribute } from '../privacy'
44import type { NodePrivacyLevelCache } from '../privacy'
55import type { RumConfiguration } from '../configuration'
66import { isElementNode } from '../../browser/htmlDomUtils'
@@ -16,6 +16,8 @@ export function getActionNameFromElement(
1616 rumConfiguration : RumConfiguration ,
1717 nodePrivacyLevel : NodePrivacyLevel = NodePrivacyLevel . ALLOW
1818) : ActionName {
19+ const nodePrivacyLevelCache : NodePrivacyLevelCache = new Map ( )
20+
1921 const { actionNameAttribute : userProgrammaticAttribute } = rumConfiguration
2022
2123 // Proceed to get the action name in two steps:
@@ -36,8 +38,8 @@ export function getActionNameFromElement(
3638 }
3739
3840 return (
39- getActionNameFromElementForStrategies ( element , priorityStrategies , rumConfiguration ) ||
40- getActionNameFromElementForStrategies ( element , fallbackStrategies , rumConfiguration ) || {
41+ getActionNameFromElementForStrategies ( element , priorityStrategies , rumConfiguration , nodePrivacyLevelCache ) ||
42+ getActionNameFromElementForStrategies ( element , fallbackStrategies , rumConfiguration , nodePrivacyLevelCache ) || {
4143 name : '' ,
4244 nameSource : ActionNameSource . BLANK ,
4345 }
@@ -58,14 +60,15 @@ function getActionNameFromElementProgrammatically(targetElement: Element, progra
5860
5961type NameStrategy = (
6062 element : Element | HTMLElement | HTMLInputElement | HTMLSelectElement ,
61- rumConfiguration : RumConfiguration
63+ rumConfiguration : RumConfiguration ,
64+ nodePrivacyLevelCache : NodePrivacyLevelCache
6265) => ActionName | undefined | null
6366
6467const priorityStrategies : NameStrategy [ ] = [
6568 // associated LABEL text
66- ( element , rumConfiguration ) => {
69+ ( element , rumConfiguration , nodePrivacyLevelCache ) => {
6770 if ( 'labels' in element && element . labels && element . labels . length > 0 ) {
68- return getActionNameFromTextualContent ( element . labels [ 0 ] , rumConfiguration )
71+ return getActionNameFromTextualContent ( element . labels [ 0 ] , rumConfiguration , nodePrivacyLevelCache )
6972 }
7073 } ,
7174 // INPUT button (and associated) value
@@ -79,41 +82,47 @@ const priorityStrategies: NameStrategy[] = [
7982 }
8083 } ,
8184 // BUTTON, LABEL or button-like element text
82- ( element , rumConfiguration ) => {
85+ ( element , rumConfiguration , nodePrivacyLevelCache ) => {
8386 if ( element . nodeName === 'BUTTON' || element . nodeName === 'LABEL' || element . getAttribute ( 'role' ) === 'button' ) {
84- return getActionNameFromTextualContent ( element , rumConfiguration )
87+ return getActionNameFromTextualContent ( element , rumConfiguration , nodePrivacyLevelCache )
8588 }
8689 } ,
87- ( element , rumConfiguration ) => getActionNameFromStandardAttribute ( element , 'aria-label' , rumConfiguration ) ,
90+ ( element , rumConfiguration , nodePrivacyLevelCache ) =>
91+ getActionNameFromStandardAttribute ( element , 'aria-label' , rumConfiguration , nodePrivacyLevelCache ) ,
8892 // associated element text designated by the aria-labelledby attribute
89- ( element , rumConfiguration ) => {
93+ ( element , rumConfiguration , nodePrivacyLevelCache ) => {
9094 const labelledByAttribute = element . getAttribute ( 'aria-labelledby' )
9195 if ( labelledByAttribute ) {
9296 return {
9397 name : labelledByAttribute
9498 . split ( / \s + / )
9599 . map ( ( id ) => getElementById ( element , id ) )
96100 . filter ( ( label ) : label is HTMLElement => Boolean ( label ) )
97- . map ( ( element ) => getTextualContent ( element , rumConfiguration ) )
101+ . map ( ( element ) => getTextualContent ( element , rumConfiguration , nodePrivacyLevelCache ) )
98102 . join ( ' ' ) ,
99103 nameSource : ActionNameSource . TEXT_CONTENT ,
100104 }
101105 }
102106 } ,
103- ( element , rumConfiguration ) => getActionNameFromStandardAttribute ( element , 'alt' , rumConfiguration ) ,
104- ( element , rumConfiguration ) => getActionNameFromStandardAttribute ( element , 'name' , rumConfiguration ) ,
105- ( element , rumConfiguration ) => getActionNameFromStandardAttribute ( element , 'title' , rumConfiguration ) ,
106- ( element , rumConfiguration ) => getActionNameFromStandardAttribute ( element , 'placeholder' , rumConfiguration ) ,
107+ ( element , rumConfiguration , nodePrivacyLevelCache ) =>
108+ getActionNameFromStandardAttribute ( element , 'alt' , rumConfiguration , nodePrivacyLevelCache ) ,
109+ ( element , rumConfiguration , nodePrivacyLevelCache ) =>
110+ getActionNameFromStandardAttribute ( element , 'name' , rumConfiguration , nodePrivacyLevelCache ) ,
111+ ( element , rumConfiguration , nodePrivacyLevelCache ) =>
112+ getActionNameFromStandardAttribute ( element , 'title' , rumConfiguration , nodePrivacyLevelCache ) ,
113+ ( element , rumConfiguration , nodePrivacyLevelCache ) =>
114+ getActionNameFromStandardAttribute ( element , 'placeholder' , rumConfiguration , nodePrivacyLevelCache ) ,
107115 // SELECT first OPTION text
108- ( element , rumConfiguration ) => {
116+ ( element , rumConfiguration , nodePrivacyLevelCache ) => {
109117 if ( 'options' in element && element . options . length > 0 ) {
110- return getActionNameFromTextualContent ( element . options [ 0 ] , rumConfiguration )
118+ return getActionNameFromTextualContent ( element . options [ 0 ] , rumConfiguration , nodePrivacyLevelCache )
111119 }
112120 } ,
113121]
114122
115123const fallbackStrategies : NameStrategy [ ] = [
116- ( element , rumConfiguration ) => getActionNameFromTextualContent ( element , rumConfiguration ) ,
124+ ( element , rumConfiguration , nodePrivacyLevelCache ) =>
125+ getActionNameFromTextualContent ( element , rumConfiguration , nodePrivacyLevelCache ) ,
117126]
118127
119128/**
@@ -124,7 +133,8 @@ const MAX_PARENTS_TO_CONSIDER = 10
124133function getActionNameFromElementForStrategies (
125134 targetElement : Element ,
126135 strategies : NameStrategy [ ] ,
127- rumConfiguration : RumConfiguration
136+ rumConfiguration : RumConfiguration ,
137+ nodePrivacyLevelCache : NodePrivacyLevelCache
128138) {
129139 let element : Element | null = targetElement
130140 let recursionCounter = 0
@@ -136,7 +146,7 @@ function getActionNameFromElementForStrategies(
136146 element . nodeName !== 'HEAD'
137147 ) {
138148 for ( const strategy of strategies ) {
139- const actionName = strategy ( element , rumConfiguration )
149+ const actionName = strategy ( element , rumConfiguration , nodePrivacyLevelCache )
140150 if ( actionName ) {
141151 const { name, nameSource } = actionName
142152 const trimmedName = name && name . trim ( )
@@ -172,31 +182,44 @@ function getElementById(refElement: Element, id: string) {
172182function getActionNameFromStandardAttribute (
173183 element : Element | HTMLElement ,
174184 attribute : string ,
175- rumConfiguration : RumConfiguration
185+ rumConfiguration : RumConfiguration ,
186+ nodePrivacyLevelCache : NodePrivacyLevelCache
176187) : ActionName {
177188 const { enablePrivacyForActionName, defaultPrivacyLevel } = rumConfiguration
178- const nodeSelfPrivacyLevel = getNodePrivacyLevel ( element , defaultPrivacyLevel )
179- const attributeValue = element . getAttribute ( attribute )
189+ let attributeValue = element . getAttribute ( attribute )
190+ if ( attributeValue && enablePrivacyForActionName ) {
191+ const nodePrivacyLevel = getNodePrivacyLevel ( element , defaultPrivacyLevel , nodePrivacyLevelCache )
192+ if (
193+ shouldMaskAttribute ( element . tagName , attribute , nodePrivacyLevel , rumConfiguration , attributeValue )
194+ ) {
195+ attributeValue = maskDisallowedTextContent ( attributeValue , ACTION_NAME_PLACEHOLDER )
196+ }
197+ } else if ( ! attributeValue ) {
198+ attributeValue = ''
199+ }
200+
180201 return {
181- name :
182- enablePrivacyForActionName && nodeSelfPrivacyLevel && shouldMaskNode ( element , nodeSelfPrivacyLevel , false )
183- ? maskDisallowedTextContent ( attributeValue || '' , ACTION_NAME_PLACEHOLDER )
184- : attributeValue || '' ,
202+ name : attributeValue ,
185203 nameSource : ActionNameSource . STANDARD_ATTRIBUTE ,
186204 }
187205}
188206
189207function getActionNameFromTextualContent (
190208 element : Element | HTMLElement ,
191- rumConfiguration : RumConfiguration
209+ rumConfiguration : RumConfiguration ,
210+ nodePrivacyLevelCache : NodePrivacyLevelCache
192211) : ActionName {
193212 return {
194- name : getTextualContent ( element , rumConfiguration ) || '' ,
213+ name : getTextualContent ( element , rumConfiguration , nodePrivacyLevelCache ) || '' ,
195214 nameSource : ActionNameSource . TEXT_CONTENT ,
196215 }
197216}
198217
199- function getTextualContent ( element : Element , rumConfiguration : RumConfiguration ) {
218+ function getTextualContent (
219+ element : Element ,
220+ rumConfiguration : RumConfiguration ,
221+ nodePrivacyLevelCache : NodePrivacyLevelCache
222+ ) {
200223 if ( ( element as HTMLElement ) . isContentEditable ) {
201224 return
202225 }
@@ -212,7 +235,8 @@ function getTextualContent(element: Element, rumConfiguration: RumConfiguration)
212235 element ,
213236 userProgrammaticAttribute ,
214237 enablePrivacyForActionName ,
215- defaultPrivacyLevel
238+ defaultPrivacyLevel ,
239+ nodePrivacyLevelCache
216240 )
217241 }
218242
@@ -256,10 +280,9 @@ function getTextualContentWithTreeWalker(
256280 element : Element ,
257281 userProgrammaticAttribute : string | undefined ,
258282 privacyEnabledActionName : boolean ,
259- defaultPrivacyLevel : NodePrivacyLevel
283+ defaultPrivacyLevel : NodePrivacyLevel ,
284+ nodePrivacyLevelCache : NodePrivacyLevelCache
260285) {
261- const nodePrivacyLevelCache : NodePrivacyLevelCache = new Map ( )
262-
263286 const walker = document . createTreeWalker (
264287 element ,
265288 // eslint-disable-next-line no-bitwise
0 commit comments