|
1 | 1 | import { captureException, getCurrentHub, withScope } from '@sentry/core';
|
2 | 2 | import { Event as SentryEvent, Mechanism, WrappedFunction } from '@sentry/types';
|
3 |
| -import { addExceptionTypeValue, htmlTreeAsString, normalize } from '@sentry/utils'; |
| 3 | +import { addExceptionTypeValue, isString, normalize } from '@sentry/utils'; |
4 | 4 |
|
5 | 5 | const debounceDuration: number = 1000;
|
6 | 6 | let keypressTimeout: number | undefined;
|
@@ -181,7 +181,7 @@ export function breadcrumbEventHandler(eventName: string): (event: Event) => voi
|
181 | 181 | // can throw an exception in some circumstances.
|
182 | 182 | let target;
|
183 | 183 | try {
|
184 |
| - target = htmlTreeAsString(event.target as Node); |
| 184 | + target = _htmlTreeAsString(event.target as Node); |
185 | 185 | } catch (e) {
|
186 | 186 | target = '<unknown>';
|
187 | 187 | }
|
@@ -240,3 +240,79 @@ export function keypressEventHandler(): (event: Event) => void {
|
240 | 240 | }, debounceDuration) as any) as number;
|
241 | 241 | };
|
242 | 242 | }
|
| 243 | + |
| 244 | +/** |
| 245 | + * Given a child DOM element, returns a query-selector statement describing that |
| 246 | + * and its ancestors |
| 247 | + * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz] |
| 248 | + * @returns generated DOM path |
| 249 | + */ |
| 250 | +function _htmlTreeAsString(elem: Node): string { |
| 251 | + let currentElem: Node | null = elem; |
| 252 | + const MAX_TRAVERSE_HEIGHT = 5; |
| 253 | + const MAX_OUTPUT_LEN = 80; |
| 254 | + const out = []; |
| 255 | + let height = 0; |
| 256 | + let len = 0; |
| 257 | + const separator = ' > '; |
| 258 | + const sepLength = separator.length; |
| 259 | + let nextStr; |
| 260 | + |
| 261 | + while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) { |
| 262 | + nextStr = _htmlElementAsString(currentElem as HTMLElement); |
| 263 | + // bail out if |
| 264 | + // - nextStr is the 'html' element |
| 265 | + // - the length of the string that would be created exceeds MAX_OUTPUT_LEN |
| 266 | + // (ignore this limit if we are on the first iteration) |
| 267 | + if (nextStr === 'html' || (height > 1 && len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN)) { |
| 268 | + break; |
| 269 | + } |
| 270 | + |
| 271 | + out.push(nextStr); |
| 272 | + |
| 273 | + len += nextStr.length; |
| 274 | + currentElem = currentElem.parentNode; |
| 275 | + } |
| 276 | + |
| 277 | + return out.reverse().join(separator); |
| 278 | +} |
| 279 | + |
| 280 | +/** |
| 281 | + * Returns a simple, query-selector representation of a DOM element |
| 282 | + * e.g. [HTMLElement] => input#foo.btn[name=baz] |
| 283 | + * @returns generated DOM path |
| 284 | + */ |
| 285 | +function _htmlElementAsString(elem: HTMLElement): string { |
| 286 | + const out = []; |
| 287 | + let className; |
| 288 | + let classes; |
| 289 | + let key; |
| 290 | + let attr; |
| 291 | + let i; |
| 292 | + |
| 293 | + if (!elem || !elem.tagName) { |
| 294 | + return ''; |
| 295 | + } |
| 296 | + |
| 297 | + out.push(elem.tagName.toLowerCase()); |
| 298 | + if (elem.id) { |
| 299 | + out.push(`#${elem.id}`); |
| 300 | + } |
| 301 | + |
| 302 | + className = elem.className; |
| 303 | + if (className && isString(className)) { |
| 304 | + classes = className.split(/\s+/); |
| 305 | + for (i = 0; i < classes.length; i++) { |
| 306 | + out.push(`.${classes[i]}`); |
| 307 | + } |
| 308 | + } |
| 309 | + const attrWhitelist = ['type', 'name', 'title', 'alt']; |
| 310 | + for (i = 0; i < attrWhitelist.length; i++) { |
| 311 | + key = attrWhitelist[i]; |
| 312 | + attr = elem.getAttribute(key); |
| 313 | + if (attr) { |
| 314 | + out.push(`[${key}="${attr}"]`); |
| 315 | + } |
| 316 | + } |
| 317 | + return out.join(''); |
| 318 | +} |
0 commit comments