long-stack-trace-zone.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. 'use strict';
  2. /**
  3. * @license Angular v<unknown>
  4. * (c) 2010-2022 Google LLC. https://angular.io/
  5. * License: MIT
  6. */
  7. /**
  8. * @fileoverview
  9. * @suppress {globalThis}
  10. */
  11. const NEWLINE = '\n';
  12. const IGNORE_FRAMES = {};
  13. const creationTrace = '__creationTrace__';
  14. const ERROR_TAG = 'STACKTRACE TRACKING';
  15. const SEP_TAG = '__SEP_TAG__';
  16. let sepTemplate = SEP_TAG + '@[native]';
  17. class LongStackTrace {
  18. constructor() {
  19. this.error = getStacktrace();
  20. this.timestamp = new Date();
  21. }
  22. }
  23. function getStacktraceWithUncaughtError() {
  24. return new Error(ERROR_TAG);
  25. }
  26. function getStacktraceWithCaughtError() {
  27. try {
  28. throw getStacktraceWithUncaughtError();
  29. }
  30. catch (err) {
  31. return err;
  32. }
  33. }
  34. // Some implementations of exception handling don't create a stack trace if the exception
  35. // isn't thrown, however it's faster not to actually throw the exception.
  36. const error = getStacktraceWithUncaughtError();
  37. const caughtError = getStacktraceWithCaughtError();
  38. const getStacktrace = error.stack ?
  39. getStacktraceWithUncaughtError :
  40. (caughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError);
  41. function getFrames(error) {
  42. return error.stack ? error.stack.split(NEWLINE) : [];
  43. }
  44. function addErrorStack(lines, error) {
  45. let trace = getFrames(error);
  46. for (let i = 0; i < trace.length; i++) {
  47. const frame = trace[i];
  48. // Filter out the Frames which are part of stack capturing.
  49. if (!IGNORE_FRAMES.hasOwnProperty(frame)) {
  50. lines.push(trace[i]);
  51. }
  52. }
  53. }
  54. function renderLongStackTrace(frames, stack) {
  55. const longTrace = [stack ? stack.trim() : ''];
  56. if (frames) {
  57. let timestamp = new Date().getTime();
  58. for (let i = 0; i < frames.length; i++) {
  59. const traceFrames = frames[i];
  60. const lastTime = traceFrames.timestamp;
  61. let separator = `____________________Elapsed ${timestamp - lastTime.getTime()} ms; At: ${lastTime}`;
  62. separator = separator.replace(/[^\w\d]/g, '_');
  63. longTrace.push(sepTemplate.replace(SEP_TAG, separator));
  64. addErrorStack(longTrace, traceFrames.error);
  65. timestamp = lastTime.getTime();
  66. }
  67. }
  68. return longTrace.join(NEWLINE);
  69. }
  70. // if Error.stackTraceLimit is 0, means stack trace
  71. // is disabled, so we don't need to generate long stack trace
  72. // this will improve performance in some test(some test will
  73. // set stackTraceLimit to 0, https://github.com/angular/zone.js/issues/698
  74. function stackTracesEnabled() {
  75. // Cast through any since this property only exists on Error in the nodejs
  76. // typings.
  77. return Error.stackTraceLimit > 0;
  78. }
  79. Zone['longStackTraceZoneSpec'] = {
  80. name: 'long-stack-trace',
  81. longStackTraceLimit: 10,
  82. // add a getLongStackTrace method in spec to
  83. // handle handled reject promise error.
  84. getLongStackTrace: function (error) {
  85. if (!error) {
  86. return undefined;
  87. }
  88. const trace = error[Zone.__symbol__('currentTaskTrace')];
  89. if (!trace) {
  90. return error.stack;
  91. }
  92. return renderLongStackTrace(trace, error.stack);
  93. },
  94. onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) {
  95. if (stackTracesEnabled()) {
  96. const currentTask = Zone.currentTask;
  97. let trace = currentTask && currentTask.data && currentTask.data[creationTrace] || [];
  98. trace = [new LongStackTrace()].concat(trace);
  99. if (trace.length > this.longStackTraceLimit) {
  100. trace.length = this.longStackTraceLimit;
  101. }
  102. if (!task.data)
  103. task.data = {};
  104. if (task.type === 'eventTask') {
  105. // Fix issue https://github.com/angular/zone.js/issues/1195,
  106. // For event task of browser, by default, all task will share a
  107. // singleton instance of data object, we should create a new one here
  108. // The cast to `any` is required to workaround a closure bug which wrongly applies
  109. // URL sanitization rules to .data access.
  110. task.data = { ...task.data };
  111. }
  112. task.data[creationTrace] = trace;
  113. }
  114. return parentZoneDelegate.scheduleTask(targetZone, task);
  115. },
  116. onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) {
  117. if (stackTracesEnabled()) {
  118. const parentTask = Zone.currentTask || error.task;
  119. if (error instanceof Error && parentTask) {
  120. const longStack = renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack);
  121. try {
  122. error.stack = error.longStack = longStack;
  123. }
  124. catch (err) {
  125. }
  126. }
  127. }
  128. return parentZoneDelegate.handleError(targetZone, error);
  129. }
  130. };
  131. function captureStackTraces(stackTraces, count) {
  132. if (count > 0) {
  133. stackTraces.push(getFrames((new LongStackTrace()).error));
  134. captureStackTraces(stackTraces, count - 1);
  135. }
  136. }
  137. function computeIgnoreFrames() {
  138. if (!stackTracesEnabled()) {
  139. return;
  140. }
  141. const frames = [];
  142. captureStackTraces(frames, 2);
  143. const frames1 = frames[0];
  144. const frames2 = frames[1];
  145. for (let i = 0; i < frames1.length; i++) {
  146. const frame1 = frames1[i];
  147. if (frame1.indexOf(ERROR_TAG) == -1) {
  148. let match = frame1.match(/^\s*at\s+/);
  149. if (match) {
  150. sepTemplate = match[0] + SEP_TAG + ' (http://localhost)';
  151. break;
  152. }
  153. }
  154. }
  155. for (let i = 0; i < frames1.length; i++) {
  156. const frame1 = frames1[i];
  157. const frame2 = frames2[i];
  158. if (frame1 === frame2) {
  159. IGNORE_FRAMES[frame1] = true;
  160. }
  161. else {
  162. break;
  163. }
  164. }
  165. }
  166. computeIgnoreFrames();