Skip to content

Commit 49eecfc

Browse files
authored
fix: Make dynamic DOM event handlers configurable (#129)
1 parent 900ee62 commit 49eecfc

File tree

9 files changed

+350
-10
lines changed

9 files changed

+350
-10
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>RUM Integ Test</title>
5+
<script src="./loader_dom_event_mutation_observer_enabled.js"></script>
6+
<link
7+
rel="icon"
8+
type="image/png"
9+
href="https://awsmedia.s3.amazonaws.com/favicon.ico"
10+
/>
11+
12+
<script>
13+
// Common to all test pages
14+
function dispatch() {
15+
cwr('dispatch');
16+
}
17+
18+
function clearRequestResponse() {
19+
document.getElementById('request_url').innerText = '';
20+
document.getElementById('request_header').innerText = '';
21+
document.getElementById('request_body').innerText = '';
22+
23+
document.getElementById('response_status').innerText = '';
24+
document.getElementById('response_header').innerText = '';
25+
document.getElementById('response_body').innerText = '';
26+
}
27+
28+
// Specific to DOM Event plugin
29+
function disable() {
30+
cwr('disable');
31+
}
32+
33+
function enable() {
34+
cwr('enable');
35+
}
36+
37+
function dynamicallyCreateButton() {
38+
const parentButton = document.getElementById(
39+
'dynamicallyCreateButton'
40+
);
41+
const newButton = document.createElement('button');
42+
newButton.innerHTML = 'Button Four';
43+
newButton.id = 'button4';
44+
newButton.setAttribute('label', 'label1');
45+
parentButton.insertAdjacentElement('afterend', newButton);
46+
}
47+
48+
function registerDomEvents() {
49+
cwr('registerDomEvents', [
50+
{ event: 'click', cssLocator: '[label="label2"]' }
51+
]);
52+
}
53+
</script>
54+
55+
<style>
56+
table {
57+
border-collapse: collapse;
58+
margin-top: 10px;
59+
margin-bottom: 10px;
60+
}
61+
62+
td,
63+
th {
64+
border: 1px solid black;
65+
text-align: left;
66+
padding: 8px;
67+
}
68+
</style>
69+
</head>
70+
<body>
71+
<p id="welcome">This application is used for RUM integ testing.</p>
72+
<hr />
73+
<button id="disable" onclick="disable()">Disable</button>
74+
<button id="enable" onclick="enable()">Enable</button>
75+
<hr />
76+
<button id="button1">Button One</button>
77+
<a> Link One </a>
78+
<hr />
79+
<button id="button2" label="label1">Button Two</button>
80+
<button id="button3" label="label1">Button Three</button>
81+
<hr />
82+
<button
83+
id="dynamicallyCreateButton"
84+
onclick="dynamicallyCreateButton()"
85+
>
86+
Add Button
87+
</button>
88+
<button id="registerDomEvents" onclick="registerDomEvents()">
89+
Update Plugin
90+
</button>
91+
<button id="button5" label="label2">Button Five</button>
92+
<hr />
93+
<button id="dispatch" onclick="dispatch()">Dispatch</button>
94+
<button id="clearRequestResponse" onclick="clearRequestResponse()">
95+
Clear
96+
</button>
97+
<hr />
98+
<span id="request"></span>
99+
<span id="response"></span>
100+
<table>
101+
<tr>
102+
<td>Request URL</td>
103+
<td id="request_url"></td>
104+
</tr>
105+
<tr>
106+
<td>Request Header</td>
107+
<td id="request_header"></td>
108+
</tr>
109+
<tr>
110+
<td>Request Body</td>
111+
<td id="request_body"></td>
112+
</tr>
113+
</table>
114+
<table>
115+
<tr>
116+
<td>Response Status Code</td>
117+
<td id="response_status"></td>
118+
</tr>
119+
<tr>
120+
<td>Response Header</td>
121+
<td id="response_header"></td>
122+
</tr>
123+
<tr>
124+
<td>Response Body</td>
125+
<td id="response_body"></td>
126+
</tr>
127+
</table>
128+
</body>
129+
</html>

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ telemetries: [
9191

9292
| Name | Type | Default | Description |
9393
| --- | --- | --- | --- |
94+
| enableMutationObserver | Boolean | `false` | When `false`, the web client will record events on only DOM elements that existed when the `window.load` event was fired.<br/><br/>When `true`, the web client will record events on all DOM elements, including those added to the DOM after the `window.load` event was fired. The web client does this by using a [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to listen for changes to the DOM. Using this feature does not typically have a perceptible impact on application performance, but may have a small impact when (1) the plugin is listening for an unusually large number DOM events (i.e., multiple thousands), or (2) the number and size of the DOM mutations are unusually large (i.e., multiple thousands). |
9495
| events | Array | `[]` | An array of target DOM events to record. Each DOM event is defined by an *event* and a *selector*. The event must be a [DOM event](https://www.w3schools.com/jsref/dom_obj_event.asp). The selector must be one of (1) `cssLocator`, (2) `elementId` or (3) `element`.<br/><br/>When two or more selectors are provided for a target DOM event, only one selector will be used. The selectors will be honored with the following precedence: (1) `cssLocator`, (2) `elementId` or (3) `element`. For example, if both `cssLocator` and `elementId` are provided, only the `cssLocator` selector will be used.<br/><br/>**Examples:**<br/>Record all elements identified by CSS selector `[label="label1"]`:<br/> `[{ event: 'click', cssLocator: '[label="label1"]' }]`<br/><br/>Record a single element with ID `mybutton`:<br/>`[{ event: 'click', elementId: 'mybutton' }]`<br/><br/>Record a complete clickstream<br/>`[{ event: 'click', element: document }]`. |
9596

9697
## Performance
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { loader } from './loader';
2+
import { showRequestClientBuilder } from '../test-utils/mock-http-handler';
3+
import { DomEventPlugin } from '../plugins/event-plugins/DomEventPlugin';
4+
loader('cwr', 'abc123', '1.0', 'us-west-2', './rum_javascript_telemetry.js', {
5+
allowCookies: true,
6+
dispatchInterval: 0,
7+
metaDataPluginsToLoad: [],
8+
eventPluginsToLoad: [
9+
new DomEventPlugin({
10+
enableMutationObserver: true,
11+
events: [
12+
{
13+
event: 'click',
14+
elementId: 'button4'
15+
},
16+
{
17+
event: 'click',
18+
cssLocator: '[label="label1"]'
19+
}
20+
]
21+
})
22+
],
23+
telemetries: [],
24+
clientBuilder: showRequestClientBuilder
25+
});
26+
window.cwr('setAwsCredentials', {
27+
accessKeyId: 'a',
28+
secretAccessKey: 'b',
29+
sessionToken: 'c'
30+
});

src/orchestration/__tests__/Orchestration.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,14 @@ describe('Orchestration tests', () => {
333333
expect(actual.sort()).toEqual(expected.sort());
334334
});
335335

336-
test('when an additional DOM event is provided then it is added to the DOM event plugin config', async () => {
336+
test('when the DOM event plugin is updated then provided target event is set to be the target DOM event', async () => {
337337
// Init
338338
const orchestration = new Orchestration('a', 'c', 'us-east-1', {
339-
eventPluginsToLoad: [new DomEventPlugin()]
339+
eventPluginsToLoad: [
340+
new DomEventPlugin({
341+
events: [{ event: 'click', elementId: 'button1' }]
342+
})
343+
]
340344
});
341345

342346
orchestration.registerDomEvents([

src/plugins/event-plugins/DomEventPlugin.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@ export type TargetDomEvent = {
2727
};
2828

2929
export type PartialDomEventPluginConfig = {
30+
enableMutationObserver?: boolean;
3031
events?: TargetDomEvent[];
3132
};
3233

3334
export type DomEventPluginConfig = {
35+
enableMutationObserver?: boolean;
3436
events: TargetDomEvent[];
3537
};
3638

3739
const defaultConfig: DomEventPluginConfig = {
40+
enableMutationObserver: false,
3841
events: []
3942
};
4043

@@ -67,11 +70,19 @@ export class DomEventPlugin implements Plugin {
6770
}
6871

6972
enable(): void {
73+
if (document.readyState !== 'complete') {
74+
window.addEventListener('load', () => this.enable());
75+
return;
76+
}
77+
7078
if (this.enabled) {
7179
return;
7280
}
7381
this.addListeners();
74-
this.observeDOMMutation();
82+
83+
if (this.config.enableMutationObserver) {
84+
this.observeDOMMutation();
85+
}
7586
this.enabled = true;
7687
}
7788

@@ -80,7 +91,9 @@ export class DomEventPlugin implements Plugin {
8091
return;
8192
}
8293
this.removeListeners();
83-
this.observer.disconnect();
94+
if (this.observer) {
95+
this.observer.disconnect();
96+
}
8497
this.enabled = false;
8598
}
8699

src/plugins/event-plugins/__integ__/DomEventPlugin.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ test('when new DOM events are registered and then a button is clicked, the event
300300
}
301301
});
302302

303-
test('when listening for a click on a dynamically added element given an element id, the event is recorded', async (t: TestController) => {
303+
test('when enableMutationObserver is false by default and listening for a click on a dynamically added element given an element id, the event is not recorded', async (t: TestController) => {
304304
// If we click too soon, the client/event collector plugin will not be loaded and will not record the click.
305305
// This could be a symptom of an issue with RUM web client load speed, or prioritization of script execution.
306306
await t
@@ -329,9 +329,12 @@ test('when listening for a click on a dynamically added element given an element
329329
event: 'click',
330330
elementId: 'button4'
331331
});
332+
333+
// plugin initialized to listen to click events on document so one event will be recorded
334+
await t.expect(events.length).eql(1);
332335
});
333336

334-
test('when listening for a click on a dynamically added element given a CSS locator, the event is recorded', async (t: TestController) => {
337+
test('when enableMutationObserver is false by default and listening for a click on a dynamically added element given a CSS locator, the event is not recorded', async (t: TestController) => {
335338
// If we click too soon, the client/event collector plugin will not be loaded and will not record the click.
336339
// This could be a symptom of an issue with RUM web client load speed, or prioritization of script execution.
337340
await t
@@ -359,14 +362,14 @@ test('when listening for a click on a dynamically added element given a CSS loca
359362
.expect(eventType)
360363
.eql(DOM_EVENT_TYPE)
361364
.expect(eventDetails)
362-
.contains({
365+
.notContains({
363366
event: 'click',
364367
cssLocator: '[label="label1"]'
365368
});
366369
}
367370
});
368371

369-
test('when listening for a click given an element id on an existing element and a dynamically added element, both events are recorded', async (t: TestController) => {
372+
test('when enableMutationObserver is false by default and listening for a click given a CSS selector on an existing element and a dynamically added element, only one event is recorded', async (t: TestController) => {
370373
// If we click too soon, the client/event collector plugin will not be loaded and will not record the click.
371374
// This could be a symptom of an issue with RUM web client load speed, or prioritization of script execution.
372375
await t
@@ -391,7 +394,7 @@ test('when listening for a click given an element id on an existing element and
391394

392395
await t
393396
.expect(events.length)
394-
.eql(2)
397+
.eql(1)
395398
.expect(eventType)
396399
.eql(DOM_EVENT_TYPE)
397400
.expect(eventDetails)

0 commit comments

Comments
 (0)