@adobe/helix-rum-enhancer 2.25.0 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/modules/dom.js +56 -56
- package/modules/index.js +55 -38
- package/modules/martech.js +6 -6
- package/modules/utils.js +5 -3
- package/package.json +3 -3
- package/src/index.js +110 -101
- package/src/index.md5 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.26.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.25.0...v2.26.0) (2024-10-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* handle pre-speculation rules and rendering ([#301](https://github.com/adobe/helix-rum-enhancer/issues/301)) ([196d404](https://github.com/adobe/helix-rum-enhancer/commit/196d404f3f90a247a223e8ddc7c667592a87319f))
|
|
7
|
+
|
|
1
8
|
# [2.25.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.24.0...v2.25.0) (2024-10-03)
|
|
2
9
|
|
|
3
10
|
|
package/modules/dom.js
CHANGED
|
@@ -9,99 +9,99 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
export const getTargetValue = (
|
|
13
|
-
||
|
|
12
|
+
export const getTargetValue = (el) => el.getAttribute('data-rum-target') || el.getAttribute('href')
|
|
13
|
+
|| el.currentSrc || el.getAttribute('src') || el.dataset.action || el.action;
|
|
14
14
|
|
|
15
|
-
export const targetSelector = (
|
|
15
|
+
export const targetSelector = (el) => {
|
|
16
16
|
try {
|
|
17
|
-
if (!
|
|
18
|
-
let
|
|
19
|
-
if (!
|
|
20
|
-
|
|
17
|
+
if (!el) return undefined;
|
|
18
|
+
let v = getTargetValue(el);
|
|
19
|
+
if (!v && el.tagName !== 'A' && el.closest('a')) {
|
|
20
|
+
v = getTargetValue(el.closest('a'));
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
22
|
+
if (v && !v.startsWith('https://')) {
|
|
23
23
|
// resolve relative links
|
|
24
|
-
|
|
24
|
+
v = new URL(v, window.location).href;
|
|
25
25
|
}
|
|
26
|
-
return
|
|
26
|
+
return v;
|
|
27
27
|
/* c8 ignore next 3 */
|
|
28
28
|
} catch (error) {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
function walk(
|
|
34
|
-
if (!
|
|
33
|
+
function walk(el, checkFn) {
|
|
34
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
35
35
|
return undefined;
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
return checkValue || walk(element.parentElement, checkFn);
|
|
37
|
+
return checkFn(el) || walk(el.parentElement, checkFn);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
function isDialog(
|
|
40
|
+
function isDialog(el) {
|
|
42
41
|
// doing it well
|
|
43
|
-
if (
|
|
42
|
+
if (el.tagName === 'DIALOG') return true;
|
|
44
43
|
// making the best of it
|
|
45
|
-
if (
|
|
46
|
-
if (
|
|
47
|
-
if (
|
|
44
|
+
if (el.getAttribute('role') === 'dialog') return true;
|
|
45
|
+
if (el.getAttribute('role') === 'alertdialog') return true;
|
|
46
|
+
if (el.getAttribute('aria-modal') === 'true') return true;
|
|
48
47
|
// doing it wrong
|
|
49
|
-
const
|
|
50
|
-
return (
|
|
48
|
+
const cs = window.getComputedStyle(el);
|
|
49
|
+
return (cs && cs.position === 'fixed' && cs.zIndex > 100);
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
function isButton(
|
|
54
|
-
if (
|
|
55
|
-
if (
|
|
56
|
-
if (
|
|
57
|
-
const classes = Array.from(
|
|
52
|
+
function isButton(el) {
|
|
53
|
+
if (el.tagName === 'BUTTON') return true;
|
|
54
|
+
if (el.tagName === 'INPUT' && el.getAttribute('type') === 'button') return true;
|
|
55
|
+
if (el.tagName === 'A') {
|
|
56
|
+
const classes = Array.from(el.classList);
|
|
58
57
|
return classes.some((className) => className.match(/button|cta/));
|
|
59
58
|
}
|
|
60
|
-
return
|
|
59
|
+
return el.getAttribute('role') === 'button';
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
function getSourceContext(
|
|
64
|
-
if (
|
|
65
|
-
return `form${
|
|
62
|
+
function getSourceContext(el) {
|
|
63
|
+
if (el.closest('form')) {
|
|
64
|
+
return `form${el.id ? `#${el.id}` : ''}`;
|
|
66
65
|
}
|
|
67
|
-
const block =
|
|
66
|
+
const block = el.closest('.block[data-block-name]');
|
|
68
67
|
if (block) return `.${block.getAttribute('data-block-name')}`;
|
|
69
|
-
if (walk(
|
|
70
|
-
if (
|
|
71
|
-
if (
|
|
72
|
-
if (
|
|
73
|
-
if (
|
|
74
|
-
return (walk(
|
|
68
|
+
if (walk(el, isDialog)) return 'dialog';
|
|
69
|
+
if (el.closest('nav')) return 'nav';
|
|
70
|
+
if (el.closest('header')) return 'header';
|
|
71
|
+
if (el.closest('footer')) return 'footer';
|
|
72
|
+
if (el.closest('aside')) return 'aside';
|
|
73
|
+
return (walk(el, (e) => e.id && `#${e.id}`));
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
function getSourceElement(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
function getSourceElement(el) {
|
|
77
|
+
const f = el.closest('form');
|
|
78
|
+
if (f && Array.from(f.elements).includes(el)) {
|
|
79
|
+
return (el.tagName.toLowerCase()
|
|
80
|
+
+ (['INPUT', 'BUTTON'].includes(el.tagName)
|
|
81
|
+
? `[type='${el.getAttribute('type') || ''}']`
|
|
82
82
|
: ''));
|
|
83
83
|
}
|
|
84
|
-
if (walk(
|
|
85
|
-
return
|
|
84
|
+
if (walk(el, isButton)) return 'button';
|
|
85
|
+
return el.tagName.toLowerCase().match(/^(a|img|video)$/) && el.tagName.toLowerCase();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function getSourceIdentifier(
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
91
|
-
return (
|
|
88
|
+
function getSourceIdentifier(el) {
|
|
89
|
+
if (el.id) return `#${el.id}`;
|
|
90
|
+
if (el.getAttribute('data-block-name')) return `.${el.getAttribute('data-block-name')}`;
|
|
91
|
+
return (el.classList.length > 0 && `.${el.classList[0]}`);
|
|
92
92
|
}
|
|
93
|
-
export const sourceSelector = (
|
|
93
|
+
export const sourceSelector = (el) => {
|
|
94
94
|
try {
|
|
95
|
-
if (!
|
|
95
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
96
96
|
return undefined;
|
|
97
97
|
}
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
98
|
+
if (el.getAttribute('data-rum-source')) {
|
|
99
|
+
return el.getAttribute('data-rum-source');
|
|
100
100
|
}
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
return `${
|
|
101
|
+
const ctx = getSourceContext(el.parentElement) || '';
|
|
102
|
+
const name = getSourceElement(el) || '';
|
|
103
|
+
const id = getSourceIdentifier(el) || '';
|
|
104
|
+
return `${ctx} ${name}${id}`.trim() || `"${el.textContent.substring(0, 10)}"`;
|
|
105
105
|
/* c8 ignore next 3 */
|
|
106
106
|
} catch (error) {
|
|
107
107
|
return null;
|
package/modules/index.js
CHANGED
|
@@ -25,14 +25,14 @@ import { fflags } from './fflags.js';
|
|
|
25
25
|
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum
|
|
26
26
|
/* c8 ignore next */ : {};
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// blocks mutation observer
|
|
30
29
|
// eslint-disable-next-line no-use-before-define, max-len
|
|
31
|
-
const
|
|
30
|
+
const blocksMO = window.MutationObserver ? new MutationObserver(blocksMCB)
|
|
32
31
|
/* c8 ignore next */ : {};
|
|
33
32
|
|
|
33
|
+
// media mutation observer
|
|
34
34
|
// eslint-disable-next-line no-use-before-define, max-len
|
|
35
|
-
const
|
|
35
|
+
const mediaMO = window.MutationObserver ? new MutationObserver(mediaMCB)
|
|
36
36
|
/* c8 ignore next */ : {};
|
|
37
37
|
|
|
38
38
|
function trackCheckpoint(checkpoint, data, t) {
|
|
@@ -112,9 +112,23 @@ function addCWVTracking() {
|
|
|
112
112
|
function addNavigationTracking() {
|
|
113
113
|
// enter checkpoint when referrer is not the current page url
|
|
114
114
|
const navigate = (source, type, redirectCount) => {
|
|
115
|
+
// target can be 'visible', 'hidden' (background tab) or 'prerendered' (speculation rules)
|
|
115
116
|
const payload = { source, target: document.visibilityState };
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
/* c8 ignore next 13 */
|
|
118
|
+
// prerendering cannot be tested yet with headless browsers
|
|
119
|
+
if (document.prerendering) {
|
|
120
|
+
// listen for "activation" of the current pre-rendered page
|
|
121
|
+
document.addEventListener('prerenderingchange', () => {
|
|
122
|
+
// pre-rendered page is now "activated"
|
|
123
|
+
payload.target = 'prerendered';
|
|
124
|
+
sampleRUM('navigate', payload); // prerendered navigation
|
|
125
|
+
}, {
|
|
126
|
+
once: true,
|
|
127
|
+
});
|
|
128
|
+
if (type === 'navigate') {
|
|
129
|
+
sampleRUM('prerender', payload); // prerendering page
|
|
130
|
+
}
|
|
131
|
+
} else if (type === 'reload' || source === window.location.href) {
|
|
118
132
|
sampleRUM('reload', payload);
|
|
119
133
|
} else if (type && type !== 'navigate') {
|
|
120
134
|
sampleRUM(type, payload); // back, forward, prerender, etc.
|
|
@@ -132,10 +146,10 @@ function addNavigationTracking() {
|
|
|
132
146
|
};
|
|
133
147
|
|
|
134
148
|
new PerformanceObserver((list) => list
|
|
135
|
-
.getEntries().map((
|
|
149
|
+
.getEntries().map((e) => navigate(
|
|
136
150
|
window.hlx.referrer || document.referrer,
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
e.type,
|
|
152
|
+
e.redirectCount,
|
|
139
153
|
)))
|
|
140
154
|
.observe({ type: 'navigation', buffered: true });
|
|
141
155
|
}
|
|
@@ -144,16 +158,16 @@ function addLoadResourceTracking() {
|
|
|
144
158
|
const observer = new PerformanceObserver((list) => {
|
|
145
159
|
try {
|
|
146
160
|
list.getEntries()
|
|
147
|
-
.filter((
|
|
148
|
-
.filter((
|
|
149
|
-
.filter((
|
|
150
|
-
.forEach((
|
|
151
|
-
sampleRUM('loadresource', { source:
|
|
161
|
+
.filter((e) => !e.responseStatus || e.responseStatus < 400)
|
|
162
|
+
.filter((e) => window.location.hostname === new URL(e.name).hostname)
|
|
163
|
+
.filter((e) => new URL(e.name).pathname.match('.*(\\.plain\\.html$|\\.json|graphql|api)'))
|
|
164
|
+
.forEach((e) => {
|
|
165
|
+
sampleRUM('loadresource', { source: e.name, target: Math.round(e.duration) });
|
|
152
166
|
});
|
|
153
167
|
list.getEntries()
|
|
154
|
-
.filter((
|
|
155
|
-
.forEach((
|
|
156
|
-
sampleRUM('missingresource', { source:
|
|
168
|
+
.filter((e) => e.responseStatus === 404)
|
|
169
|
+
.forEach((e) => {
|
|
170
|
+
sampleRUM('missingresource', { source: e.name, target: e.hostname });
|
|
157
171
|
});
|
|
158
172
|
/* c8 ignore next 3 */
|
|
159
173
|
} catch (error) {
|
|
@@ -163,24 +177,26 @@ function addLoadResourceTracking() {
|
|
|
163
177
|
observer.observe({ type: 'resource', buffered: true });
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
// activate blocks mutation observer
|
|
181
|
+
function activateBlocksMO() {
|
|
182
|
+
if (!blocksMO || blocksMO.active) {
|
|
168
183
|
return;
|
|
169
184
|
}
|
|
170
|
-
|
|
171
|
-
|
|
185
|
+
blocksMO.active = true;
|
|
186
|
+
blocksMO.observe(
|
|
172
187
|
document.body,
|
|
173
188
|
// eslint-disable-next-line object-curly-newline
|
|
174
189
|
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
|
|
175
190
|
);
|
|
176
191
|
}
|
|
177
192
|
|
|
178
|
-
|
|
179
|
-
|
|
193
|
+
// activate media mutation observer
|
|
194
|
+
function activateMediaMO() {
|
|
195
|
+
if (!mediaMO || mediaMO.active) {
|
|
180
196
|
return;
|
|
181
197
|
}
|
|
182
|
-
|
|
183
|
-
|
|
198
|
+
mediaMO.active = true;
|
|
199
|
+
mediaMO.observe(
|
|
184
200
|
document.body,
|
|
185
201
|
// eslint-disable-next-line object-curly-newline
|
|
186
202
|
{ subtree: true, attributes: false, childList: true },
|
|
@@ -192,16 +208,16 @@ function getIntersectionObsever(checkpoint) {
|
|
|
192
208
|
if (!window.IntersectionObserver) {
|
|
193
209
|
return null;
|
|
194
210
|
}
|
|
195
|
-
|
|
196
|
-
|
|
211
|
+
activateBlocksMO();
|
|
212
|
+
activateMediaMO();
|
|
197
213
|
const observer = new IntersectionObserver((entries) => {
|
|
198
214
|
try {
|
|
199
215
|
entries
|
|
200
|
-
.filter((
|
|
201
|
-
.forEach((
|
|
202
|
-
observer.unobserve(
|
|
203
|
-
const target = targetSelector(
|
|
204
|
-
const source = sourceSelector(
|
|
216
|
+
.filter((e) => e.isIntersecting)
|
|
217
|
+
.forEach((e) => {
|
|
218
|
+
observer.unobserve(e.target); // observe only once
|
|
219
|
+
const target = targetSelector(e.target);
|
|
220
|
+
const source = sourceSelector(e.target);
|
|
205
221
|
sampleRUM(checkpoint, { target, source });
|
|
206
222
|
});
|
|
207
223
|
/* c8 ignore next 3 */
|
|
@@ -233,11 +249,10 @@ function addViewMediaTracking(parent) {
|
|
|
233
249
|
}
|
|
234
250
|
|
|
235
251
|
function addFormTracking(parent) {
|
|
236
|
-
|
|
237
|
-
|
|
252
|
+
activateBlocksMO();
|
|
253
|
+
activateMediaMO();
|
|
238
254
|
parent.querySelectorAll('form').forEach((form) => {
|
|
239
|
-
form.
|
|
240
|
-
form.addEventListener('submit', formSubmitListener);
|
|
255
|
+
form.addEventListener('submit', (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) }), { once: true });
|
|
241
256
|
});
|
|
242
257
|
}
|
|
243
258
|
|
|
@@ -245,7 +260,8 @@ function addObserver(ck, fn, block) {
|
|
|
245
260
|
return DEFAULT_TRACKING_EVENTS.includes(ck) && fn(block);
|
|
246
261
|
}
|
|
247
262
|
|
|
248
|
-
|
|
263
|
+
// blocks mutation observer callback
|
|
264
|
+
function blocksMCB(mutations) {
|
|
249
265
|
// block specific mutations
|
|
250
266
|
mutations
|
|
251
267
|
.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
@@ -256,7 +272,8 @@ function blocksMutationsCallback(mutations) {
|
|
|
256
272
|
});
|
|
257
273
|
}
|
|
258
274
|
|
|
259
|
-
|
|
275
|
+
// media mutation observer callback
|
|
276
|
+
function mediaMCB(mutations) {
|
|
260
277
|
// media mutations
|
|
261
278
|
mutations
|
|
262
279
|
.forEach((m) => {
|
package/modules/martech.js
CHANGED
|
@@ -19,7 +19,7 @@ export function addCookieConsentTracking(sampleRUM) {
|
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let
|
|
22
|
+
let consentMO; // consent mutation observer
|
|
23
23
|
const trackShowConsent = () => {
|
|
24
24
|
const otsdk = document.querySelector('body > div#onetrust-consent-sdk');
|
|
25
25
|
if (otsdk) {
|
|
@@ -28,8 +28,8 @@ export function addCookieConsentTracking(sampleRUM) {
|
|
|
28
28
|
} else {
|
|
29
29
|
sampleRUM('consent', { source: 'onetrust', target: 'show' });
|
|
30
30
|
}
|
|
31
|
-
if (
|
|
32
|
-
|
|
31
|
+
if (consentMO) {
|
|
32
|
+
consentMO.disconnect();
|
|
33
33
|
}
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
@@ -38,11 +38,11 @@ export function addCookieConsentTracking(sampleRUM) {
|
|
|
38
38
|
|
|
39
39
|
if (!trackShowConsent()) {
|
|
40
40
|
// eslint-disable-next-line max-len
|
|
41
|
-
|
|
41
|
+
consentMO = window.MutationObserver
|
|
42
42
|
? new MutationObserver(trackShowConsent)
|
|
43
43
|
: /* c8 ignore next */ null;
|
|
44
|
-
if (
|
|
45
|
-
|
|
44
|
+
if (consentMO) {
|
|
45
|
+
consentMO.observe(
|
|
46
46
|
document.body,
|
|
47
47
|
// eslint-disable-next-line object-curly-newline
|
|
48
48
|
{ attributes: false, childList: true, subtree: false },
|
package/modules/utils.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
const { href } = window.location;
|
|
14
|
+
|
|
13
15
|
export const urlSanitizers = {
|
|
14
16
|
/**
|
|
15
17
|
* Returns the full url.
|
|
@@ -17,21 +19,21 @@ export const urlSanitizers = {
|
|
|
17
19
|
* @param {string} url (default: window.location.href) The url to sanitize
|
|
18
20
|
* @returns {string} The sanitized url
|
|
19
21
|
*/
|
|
20
|
-
full: (url =
|
|
22
|
+
full: (url = href) => new URL(url).toString(),
|
|
21
23
|
/**
|
|
22
24
|
* Returns the origin of the provided url.
|
|
23
25
|
* If no url is provided, it defaults to window.location.href.
|
|
24
26
|
* @param {string} url (default: window.location.href) The url to sanitize
|
|
25
27
|
* @returns {string} The sanitized url
|
|
26
28
|
*/
|
|
27
|
-
origin: (url =
|
|
29
|
+
origin: (url = href) => new URL(url).origin,
|
|
28
30
|
/**
|
|
29
31
|
* Returns the sanitized url: the origin and the path (no query params or hash)
|
|
30
32
|
* If no url is provided, it defaults to window.location.href.
|
|
31
33
|
* @param {string} url (default: window.location.href) The url to sanitize
|
|
32
34
|
* @returns {string} The sanitized url
|
|
33
35
|
*/
|
|
34
|
-
path: (url =
|
|
36
|
+
path: (url = href) => {
|
|
35
37
|
const u = new URL(url);
|
|
36
38
|
return `${u.origin}${u.pathname}`;
|
|
37
39
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-rum-enhancer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.0",
|
|
4
4
|
"description": "Helix RUM Enhancer",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"reporter-options": "configFile=.mocha-multi.json"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@adobe/eslint-config-helix": "2.0.
|
|
36
|
+
"@adobe/eslint-config-helix": "2.0.8",
|
|
37
37
|
"@adobe/helix-rum-js": "2.6.0",
|
|
38
38
|
"@esm-bundle/chai": "4.3.4-fix.0",
|
|
39
39
|
"@semantic-release/changelog": "6.0.3",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"codecov": "3.8.3",
|
|
48
48
|
"eslint": "8.57.1",
|
|
49
49
|
"eslint-plugin-header": "3.1.1",
|
|
50
|
-
"eslint-plugin-import": "2.
|
|
50
|
+
"eslint-plugin-import": "2.31.0",
|
|
51
51
|
"husky": "9.1.6",
|
|
52
52
|
"jsdoc-to-markdown": "9.0.2",
|
|
53
53
|
"junit-report-builder": "5.1.1",
|
package/src/index.js
CHANGED
|
@@ -29,96 +29,97 @@
|
|
|
29
29
|
const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'viewblock', 'viewmedia', 'loadresource', 'utm', 'paid', 'email', 'consent'];
|
|
30
30
|
fflags.enabled('example', () => DEFAULT_TRACKING_EVENTS.push('example'));
|
|
31
31
|
|
|
32
|
+
const { href } = window.location;
|
|
32
33
|
const urlSanitizers = {
|
|
33
|
-
full: (url =
|
|
34
|
-
origin: (url =
|
|
35
|
-
path: (url =
|
|
34
|
+
full: (url = href) => new URL(url).toString(),
|
|
35
|
+
origin: (url = href) => new URL(url).origin,
|
|
36
|
+
path: (url = href) => {
|
|
36
37
|
const u = new URL(url);
|
|
37
38
|
return `${u.origin}${u.pathname}`;
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
const getTargetValue = (
|
|
42
|
-
||
|
|
43
|
-
const targetSelector = (
|
|
42
|
+
const getTargetValue = (el) => el.getAttribute('data-rum-target') || el.getAttribute('href')
|
|
43
|
+
|| el.currentSrc || el.getAttribute('src') || el.dataset.action || el.action;
|
|
44
|
+
const targetSelector = (el) => {
|
|
44
45
|
try {
|
|
45
|
-
if (!
|
|
46
|
-
let
|
|
47
|
-
if (!
|
|
48
|
-
|
|
46
|
+
if (!el) return undefined;
|
|
47
|
+
let v = getTargetValue(el);
|
|
48
|
+
if (!v && el.tagName !== 'A' && el.closest('a')) {
|
|
49
|
+
v = getTargetValue(el.closest('a'));
|
|
49
50
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
51
|
+
if (v && !v.startsWith('https://')) {
|
|
52
|
+
v = new URL(v, window.location).href;
|
|
52
53
|
}
|
|
53
|
-
return
|
|
54
|
+
return v;
|
|
54
55
|
} catch (error) {
|
|
55
56
|
return null;
|
|
56
57
|
}
|
|
57
58
|
};
|
|
58
|
-
function walk(
|
|
59
|
-
if (!
|
|
59
|
+
function walk(el, checkFn) {
|
|
60
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
60
61
|
return undefined;
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
return checkValue || walk(element.parentElement, checkFn);
|
|
63
|
+
return checkFn(el) || walk(el.parentElement, checkFn);
|
|
64
64
|
}
|
|
65
|
-
function isDialog(
|
|
66
|
-
if (
|
|
67
|
-
if (
|
|
68
|
-
if (
|
|
69
|
-
if (
|
|
70
|
-
const
|
|
71
|
-
return (
|
|
65
|
+
function isDialog(el) {
|
|
66
|
+
if (el.tagName === 'DIALOG') return true;
|
|
67
|
+
if (el.getAttribute('role') === 'dialog') return true;
|
|
68
|
+
if (el.getAttribute('role') === 'alertdialog') return true;
|
|
69
|
+
if (el.getAttribute('aria-modal') === 'true') return true;
|
|
70
|
+
const cs = window.getComputedStyle(el);
|
|
71
|
+
return (cs && cs.position === 'fixed' && cs.zIndex > 100);
|
|
72
72
|
}
|
|
73
|
-
function isButton(
|
|
74
|
-
if (
|
|
75
|
-
if (
|
|
76
|
-
if (
|
|
77
|
-
const classes = Array.from(
|
|
73
|
+
function isButton(el) {
|
|
74
|
+
if (el.tagName === 'BUTTON') return true;
|
|
75
|
+
if (el.tagName === 'INPUT' && el.getAttribute('type') === 'button') return true;
|
|
76
|
+
if (el.tagName === 'A') {
|
|
77
|
+
const classes = Array.from(el.classList);
|
|
78
78
|
return classes.some((className) => className.match(/button|cta/));
|
|
79
79
|
}
|
|
80
|
-
return
|
|
80
|
+
return el.getAttribute('role') === 'button';
|
|
81
81
|
}
|
|
82
|
-
function getSourceContext(
|
|
83
|
-
if (
|
|
84
|
-
return `form${
|
|
82
|
+
function getSourceContext(el) {
|
|
83
|
+
if (el.closest('form')) {
|
|
84
|
+
return `form${el.id ? `#${el.id}` : ''}`;
|
|
85
85
|
}
|
|
86
|
-
const block =
|
|
86
|
+
const block = el.closest('.block[data-block-name]');
|
|
87
87
|
if (block) return `.${block.getAttribute('data-block-name')}`;
|
|
88
|
-
if (walk(
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
91
|
-
if (
|
|
92
|
-
if (
|
|
93
|
-
return (walk(
|
|
88
|
+
if (walk(el, isDialog)) return 'dialog';
|
|
89
|
+
if (el.closest('nav')) return 'nav';
|
|
90
|
+
if (el.closest('header')) return 'header';
|
|
91
|
+
if (el.closest('footer')) return 'footer';
|
|
92
|
+
if (el.closest('aside')) return 'aside';
|
|
93
|
+
return (walk(el, (e) => e.id && `#${e.id}`));
|
|
94
94
|
}
|
|
95
|
-
function getSourceElement(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
function getSourceElement(el) {
|
|
96
|
+
const f = el.closest('form');
|
|
97
|
+
if (f && Array.from(f.elements).includes(el)) {
|
|
98
|
+
return (el.tagName.toLowerCase()
|
|
99
|
+
+ (['INPUT', 'BUTTON'].includes(el.tagName)
|
|
100
|
+
? `[type='${el.getAttribute('type') || ''}']`
|
|
100
101
|
: ''));
|
|
101
102
|
}
|
|
102
|
-
if (walk(
|
|
103
|
-
return
|
|
103
|
+
if (walk(el, isButton)) return 'button';
|
|
104
|
+
return el.tagName.toLowerCase().match(/^(a|img|video)$/) && el.tagName.toLowerCase();
|
|
104
105
|
}
|
|
105
|
-
function getSourceIdentifier(
|
|
106
|
-
if (
|
|
107
|
-
if (
|
|
108
|
-
return (
|
|
106
|
+
function getSourceIdentifier(el) {
|
|
107
|
+
if (el.id) return `#${el.id}`;
|
|
108
|
+
if (el.getAttribute('data-block-name')) return `.${el.getAttribute('data-block-name')}`;
|
|
109
|
+
return (el.classList.length > 0 && `.${el.classList[0]}`);
|
|
109
110
|
}
|
|
110
|
-
const sourceSelector = (
|
|
111
|
+
const sourceSelector = (el) => {
|
|
111
112
|
try {
|
|
112
|
-
if (!
|
|
113
|
+
if (!el || el === document.body || el === document.documentElement) {
|
|
113
114
|
return undefined;
|
|
114
115
|
}
|
|
115
|
-
if (
|
|
116
|
-
return
|
|
116
|
+
if (el.getAttribute('data-rum-source')) {
|
|
117
|
+
return el.getAttribute('data-rum-source');
|
|
117
118
|
}
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
return `${
|
|
119
|
+
const ctx = getSourceContext(el.parentElement) || '';
|
|
120
|
+
const name = getSourceElement(el) || '';
|
|
121
|
+
const id = getSourceIdentifier(el) || '';
|
|
122
|
+
return `${ctx} ${name}${id}`.trim() || `"${el.textContent.substring(0, 10)}"`;
|
|
122
123
|
} catch (error) {
|
|
123
124
|
return null;
|
|
124
125
|
}
|
|
@@ -132,7 +133,7 @@
|
|
|
132
133
|
sampleRUM('consent', { source: 'onetrust', target: 'hidden' });
|
|
133
134
|
return;
|
|
134
135
|
}
|
|
135
|
-
let
|
|
136
|
+
let consentMO;
|
|
136
137
|
const trackShowConsent = () => {
|
|
137
138
|
const otsdk = document.querySelector('body > div#onetrust-consent-sdk');
|
|
138
139
|
if (otsdk) {
|
|
@@ -141,19 +142,19 @@
|
|
|
141
142
|
} else {
|
|
142
143
|
sampleRUM('consent', { source: 'onetrust', target: 'show' });
|
|
143
144
|
}
|
|
144
|
-
if (
|
|
145
|
-
|
|
145
|
+
if (consentMO) {
|
|
146
|
+
consentMO.disconnect();
|
|
146
147
|
}
|
|
147
148
|
return true;
|
|
148
149
|
}
|
|
149
150
|
return false;
|
|
150
151
|
};
|
|
151
152
|
if (!trackShowConsent()) {
|
|
152
|
-
|
|
153
|
+
consentMO = window.MutationObserver
|
|
153
154
|
? new MutationObserver(trackShowConsent)
|
|
154
155
|
: null;
|
|
155
|
-
if (
|
|
156
|
-
|
|
156
|
+
if (consentMO) {
|
|
157
|
+
consentMO.observe(
|
|
157
158
|
document.body,
|
|
158
159
|
{ attributes: false, childList: true, subtree: false },
|
|
159
160
|
);
|
|
@@ -197,10 +198,9 @@
|
|
|
197
198
|
|
|
198
199
|
const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum
|
|
199
200
|
: {};
|
|
200
|
-
const
|
|
201
|
-
const blocksMutationObserver = window.MutationObserver ? new MutationObserver(blocksMutationsCallback)
|
|
201
|
+
const blocksMO = window.MutationObserver ? new MutationObserver(blocksMCB)
|
|
202
202
|
: {};
|
|
203
|
-
const
|
|
203
|
+
const mediaMO = window.MutationObserver ? new MutationObserver(mediaMCB)
|
|
204
204
|
: {};
|
|
205
205
|
function trackCheckpoint(checkpoint, data, t) {
|
|
206
206
|
const { weight, id } = window.hlx.rum;
|
|
@@ -266,7 +266,17 @@
|
|
|
266
266
|
function addNavigationTracking() {
|
|
267
267
|
const navigate = (source, type, redirectCount) => {
|
|
268
268
|
const payload = { source, target: document.visibilityState };
|
|
269
|
-
if (
|
|
269
|
+
if (document.prerendering) {
|
|
270
|
+
document.addEventListener('prerenderingchange', () => {
|
|
271
|
+
payload.target = 'prerendered';
|
|
272
|
+
sampleRUM('navigate', payload);
|
|
273
|
+
}, {
|
|
274
|
+
once: true,
|
|
275
|
+
});
|
|
276
|
+
if (type === 'navigate') {
|
|
277
|
+
sampleRUM('prerender', payload);
|
|
278
|
+
}
|
|
279
|
+
} else if (type === 'reload' || source === window.location.href) {
|
|
270
280
|
sampleRUM('reload', payload);
|
|
271
281
|
} else if (type && type !== 'navigate') {
|
|
272
282
|
sampleRUM(type, payload);
|
|
@@ -283,10 +293,10 @@
|
|
|
283
293
|
});
|
|
284
294
|
};
|
|
285
295
|
new PerformanceObserver((list) => list
|
|
286
|
-
.getEntries().map((
|
|
296
|
+
.getEntries().map((e) => navigate(
|
|
287
297
|
window.hlx.referrer || document.referrer,
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
e.type,
|
|
299
|
+
e.redirectCount,
|
|
290
300
|
)))
|
|
291
301
|
.observe({ type: 'navigation', buffered: true });
|
|
292
302
|
}
|
|
@@ -294,38 +304,38 @@
|
|
|
294
304
|
const observer = new PerformanceObserver((list) => {
|
|
295
305
|
try {
|
|
296
306
|
list.getEntries()
|
|
297
|
-
.filter((
|
|
298
|
-
.filter((
|
|
299
|
-
.filter((
|
|
300
|
-
.forEach((
|
|
301
|
-
sampleRUM('loadresource', { source:
|
|
307
|
+
.filter((e) => !e.responseStatus || e.responseStatus < 400)
|
|
308
|
+
.filter((e) => window.location.hostname === new URL(e.name).hostname)
|
|
309
|
+
.filter((e) => new URL(e.name).pathname.match('.*(\\.plain\\.html$|\\.json|graphql|api)'))
|
|
310
|
+
.forEach((e) => {
|
|
311
|
+
sampleRUM('loadresource', { source: e.name, target: Math.round(e.duration) });
|
|
302
312
|
});
|
|
303
313
|
list.getEntries()
|
|
304
|
-
.filter((
|
|
305
|
-
.forEach((
|
|
306
|
-
sampleRUM('missingresource', { source:
|
|
314
|
+
.filter((e) => e.responseStatus === 404)
|
|
315
|
+
.forEach((e) => {
|
|
316
|
+
sampleRUM('missingresource', { source: e.name, target: e.hostname });
|
|
307
317
|
});
|
|
308
318
|
} catch (error) {
|
|
309
319
|
}
|
|
310
320
|
});
|
|
311
321
|
observer.observe({ type: 'resource', buffered: true });
|
|
312
322
|
}
|
|
313
|
-
function
|
|
314
|
-
if (!
|
|
323
|
+
function activateBlocksMO() {
|
|
324
|
+
if (!blocksMO || blocksMO.active) {
|
|
315
325
|
return;
|
|
316
326
|
}
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
blocksMO.active = true;
|
|
328
|
+
blocksMO.observe(
|
|
319
329
|
document.body,
|
|
320
330
|
{ subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
|
|
321
331
|
);
|
|
322
332
|
}
|
|
323
|
-
function
|
|
324
|
-
if (!
|
|
333
|
+
function activateMediaMO() {
|
|
334
|
+
if (!mediaMO || mediaMO.active) {
|
|
325
335
|
return;
|
|
326
336
|
}
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
mediaMO.active = true;
|
|
338
|
+
mediaMO.observe(
|
|
329
339
|
document.body,
|
|
330
340
|
{ subtree: true, attributes: false, childList: true },
|
|
331
341
|
);
|
|
@@ -334,16 +344,16 @@
|
|
|
334
344
|
if (!window.IntersectionObserver) {
|
|
335
345
|
return null;
|
|
336
346
|
}
|
|
337
|
-
|
|
338
|
-
|
|
347
|
+
activateBlocksMO();
|
|
348
|
+
activateMediaMO();
|
|
339
349
|
const observer = new IntersectionObserver((entries) => {
|
|
340
350
|
try {
|
|
341
351
|
entries
|
|
342
|
-
.filter((
|
|
343
|
-
.forEach((
|
|
344
|
-
observer.unobserve(
|
|
345
|
-
const target = targetSelector(
|
|
346
|
-
const source = sourceSelector(
|
|
352
|
+
.filter((e) => e.isIntersecting)
|
|
353
|
+
.forEach((e) => {
|
|
354
|
+
observer.unobserve(e.target);
|
|
355
|
+
const target = targetSelector(e.target);
|
|
356
|
+
const source = sourceSelector(e.target);
|
|
347
357
|
sampleRUM(checkpoint, { target, source });
|
|
348
358
|
});
|
|
349
359
|
} catch (error) {
|
|
@@ -371,17 +381,16 @@
|
|
|
371
381
|
}
|
|
372
382
|
}
|
|
373
383
|
function addFormTracking(parent) {
|
|
374
|
-
|
|
375
|
-
|
|
384
|
+
activateBlocksMO();
|
|
385
|
+
activateMediaMO();
|
|
376
386
|
parent.querySelectorAll('form').forEach((form) => {
|
|
377
|
-
form.
|
|
378
|
-
form.addEventListener('submit', formSubmitListener);
|
|
387
|
+
form.addEventListener('submit', (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) }), { once: true });
|
|
379
388
|
});
|
|
380
389
|
}
|
|
381
390
|
function addObserver(ck, fn, block) {
|
|
382
391
|
return DEFAULT_TRACKING_EVENTS.includes(ck) && fn(block);
|
|
383
392
|
}
|
|
384
|
-
function
|
|
393
|
+
function blocksMCB(mutations) {
|
|
385
394
|
mutations
|
|
386
395
|
.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
|
|
387
396
|
.filter((m) => m.target.dataset.blockStatus === 'loaded')
|
|
@@ -390,7 +399,7 @@
|
|
|
390
399
|
addObserver('viewblock', addViewBlockTracking, m.target);
|
|
391
400
|
});
|
|
392
401
|
}
|
|
393
|
-
function
|
|
402
|
+
function mediaMCB(mutations) {
|
|
394
403
|
mutations
|
|
395
404
|
.forEach((m) => {
|
|
396
405
|
addObserver('viewmedia', addViewMediaTracking, m.target);
|
package/src/index.md5
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8094422990fd7a9219bffb7309cb92a2
|