browser.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const is = require('./is.js');
  3. const worldwide = require('./worldwide.js');
  4. // eslint-disable-next-line deprecation/deprecation
  5. const WINDOW = worldwide.getGlobalObject();
  6. const DEFAULT_MAX_STRING_LENGTH = 80;
  7. /**
  8. * Given a child DOM element, returns a query-selector statement describing that
  9. * and its ancestors
  10. * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
  11. * @returns generated DOM path
  12. */
  13. function htmlTreeAsString(
  14. elem,
  15. options = {},
  16. ) {
  17. if (!elem) {
  18. return '<unknown>';
  19. }
  20. // try/catch both:
  21. // - accessing event.target (see getsentry/raven-js#838, #768)
  22. // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
  23. // - can throw an exception in some circumstances.
  24. try {
  25. let currentElem = elem ;
  26. const MAX_TRAVERSE_HEIGHT = 5;
  27. const out = [];
  28. let height = 0;
  29. let len = 0;
  30. const separator = ' > ';
  31. const sepLength = separator.length;
  32. let nextStr;
  33. const keyAttrs = Array.isArray(options) ? options : options.keyAttrs;
  34. const maxStringLength = (!Array.isArray(options) && options.maxStringLength) || DEFAULT_MAX_STRING_LENGTH;
  35. while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) {
  36. nextStr = _htmlElementAsString(currentElem, keyAttrs);
  37. // bail out if
  38. // - nextStr is the 'html' element
  39. // - the length of the string that would be created exceeds maxStringLength
  40. // (ignore this limit if we are on the first iteration)
  41. if (nextStr === 'html' || (height > 1 && len + out.length * sepLength + nextStr.length >= maxStringLength)) {
  42. break;
  43. }
  44. out.push(nextStr);
  45. len += nextStr.length;
  46. currentElem = currentElem.parentNode;
  47. }
  48. return out.reverse().join(separator);
  49. } catch (_oO) {
  50. return '<unknown>';
  51. }
  52. }
  53. /**
  54. * Returns a simple, query-selector representation of a DOM element
  55. * e.g. [HTMLElement] => input#foo.btn[name=baz]
  56. * @returns generated DOM path
  57. */
  58. function _htmlElementAsString(el, keyAttrs) {
  59. const elem = el
  60. ;
  61. const out = [];
  62. let className;
  63. let classes;
  64. let key;
  65. let attr;
  66. let i;
  67. if (!elem || !elem.tagName) {
  68. return '';
  69. }
  70. // @ts-expect-error WINDOW has HTMLElement
  71. if (WINDOW.HTMLElement) {
  72. // If using the component name annotation plugin, this value may be available on the DOM node
  73. if (elem instanceof HTMLElement && elem.dataset && elem.dataset['sentryComponent']) {
  74. return elem.dataset['sentryComponent'];
  75. }
  76. }
  77. out.push(elem.tagName.toLowerCase());
  78. // Pairs of attribute keys defined in `serializeAttribute` and their values on element.
  79. const keyAttrPairs =
  80. keyAttrs && keyAttrs.length
  81. ? keyAttrs.filter(keyAttr => elem.getAttribute(keyAttr)).map(keyAttr => [keyAttr, elem.getAttribute(keyAttr)])
  82. : null;
  83. if (keyAttrPairs && keyAttrPairs.length) {
  84. keyAttrPairs.forEach(keyAttrPair => {
  85. out.push(`[${keyAttrPair[0]}="${keyAttrPair[1]}"]`);
  86. });
  87. } else {
  88. if (elem.id) {
  89. out.push(`#${elem.id}`);
  90. }
  91. // eslint-disable-next-line prefer-const
  92. className = elem.className;
  93. if (className && is.isString(className)) {
  94. classes = className.split(/\s+/);
  95. for (i = 0; i < classes.length; i++) {
  96. out.push(`.${classes[i]}`);
  97. }
  98. }
  99. }
  100. const allowedAttrs = ['aria-label', 'type', 'name', 'title', 'alt'];
  101. for (i = 0; i < allowedAttrs.length; i++) {
  102. key = allowedAttrs[i];
  103. attr = elem.getAttribute(key);
  104. if (attr) {
  105. out.push(`[${key}="${attr}"]`);
  106. }
  107. }
  108. return out.join('');
  109. }
  110. /**
  111. * A safe form of location.href
  112. */
  113. function getLocationHref() {
  114. try {
  115. return WINDOW.document.location.href;
  116. } catch (oO) {
  117. return '';
  118. }
  119. }
  120. /**
  121. * Gets a DOM element by using document.querySelector.
  122. *
  123. * This wrapper will first check for the existance of the function before
  124. * actually calling it so that we don't have to take care of this check,
  125. * every time we want to access the DOM.
  126. *
  127. * Reason: DOM/querySelector is not available in all environments.
  128. *
  129. * We have to cast to any because utils can be consumed by a variety of environments,
  130. * and we don't want to break TS users. If you know what element will be selected by
  131. * `document.querySelector`, specify it as part of the generic call. For example,
  132. * `const element = getDomElement<Element>('selector');`
  133. *
  134. * @param selector the selector string passed on to document.querySelector
  135. */
  136. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  137. function getDomElement(selector) {
  138. if (WINDOW.document && WINDOW.document.querySelector) {
  139. return WINDOW.document.querySelector(selector) ;
  140. }
  141. return null;
  142. }
  143. /**
  144. * Given a DOM element, traverses up the tree until it finds the first ancestor node
  145. * that has the `data-sentry-component` attribute. This attribute is added at build-time
  146. * by projects that have the component name annotation plugin installed.
  147. *
  148. * @returns a string representation of the component for the provided DOM element, or `null` if not found
  149. */
  150. function getComponentName(elem) {
  151. // @ts-expect-error WINDOW has HTMLElement
  152. if (!WINDOW.HTMLElement) {
  153. return null;
  154. }
  155. let currentElem = elem ;
  156. const MAX_TRAVERSE_HEIGHT = 5;
  157. for (let i = 0; i < MAX_TRAVERSE_HEIGHT; i++) {
  158. if (!currentElem) {
  159. return null;
  160. }
  161. if (currentElem instanceof HTMLElement && currentElem.dataset['sentryComponent']) {
  162. return currentElem.dataset['sentryComponent'];
  163. }
  164. currentElem = currentElem.parentNode;
  165. }
  166. return null;
  167. }
  168. exports.getComponentName = getComponentName;
  169. exports.getDomElement = getDomElement;
  170. exports.getLocationHref = getLocationHref;
  171. exports.htmlTreeAsString = htmlTreeAsString;
  172. //# sourceMappingURL=browser.js.map