Bug 1465581 - Export Screenshots 33.0.0 to Firefox (upgrade Raven to 3.25.2); r?ianbicking draft
authorBarry Chen <bchen@mozilla.com>
Mon, 18 Jun 2018 11:01:44 -0500
changeset 808167 503dae1f3464a0aa125cb8e4e1b66997e78203d4
parent 808166 9547eaa9f227283ff341ae70fee8536adb710fd9
child 808168 2cf9020b6313589830fb87f97866207e73338127
push id113300
push userbmo:bchen@mozilla.com
push dateMon, 18 Jun 2018 16:09:31 +0000
reviewersianbicking
bugs1465581
milestone62.0a1
Bug 1465581 - Export Screenshots 33.0.0 to Firefox (upgrade Raven to 3.25.2); r?ianbicking MozReview-Commit-ID: 67A865KrF3z
browser/extensions/screenshots/webextension/build/raven.js
--- a/browser/extensions/screenshots/webextension/build/raven.js
+++ b/browser/extensions/screenshots/webextension/build/raven.js
@@ -1,9 +1,9 @@
-/*! Raven.js 3.24.1 (f3b3500) | github.com/getsentry/raven-js */
+/*! Raven.js 3.25.2 (30b6d4e) | github.com/getsentry/raven-js */
 
 /*
  * Includes TraceKit
  * https://github.com/getsentry/TraceKit
  *
  * Copyright 2018 Matt Robenolt and other contributors
  * Released under the BSD license
  * https://github.com/getsentry/raven-js/blob/master/LICENSE
@@ -69,20 +69,22 @@ module.exports = {
 /*global XDomainRequest:false */
 
 var TraceKit = _dereq_(6);
 var stringify = _dereq_(7);
 var md5 = _dereq_(8);
 var RavenConfigError = _dereq_(1);
 
 var utils = _dereq_(5);
+var isErrorEvent = utils.isErrorEvent;
+var isDOMError = utils.isDOMError;
+var isDOMException = utils.isDOMException;
 var isError = utils.isError;
 var isObject = utils.isObject;
 var isPlainObject = utils.isPlainObject;
-var isErrorEvent = utils.isErrorEvent;
 var isUndefined = utils.isUndefined;
 var isFunction = utils.isFunction;
 var isString = utils.isString;
 var isArray = utils.isArray;
 var isEmptyObject = utils.isEmptyObject;
 var each = utils.each;
 var objectMerge = utils.objectMerge;
 var truncate = utils.truncate;
@@ -110,17 +112,21 @@ var dsnKeys = 'source protocol user pass
 function now() {
   return +new Date();
 }
 
 // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
 var _window =
   typeof window !== 'undefined'
     ? window
-    : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+    : typeof global !== 'undefined'
+      ? global
+      : typeof self !== 'undefined'
+        ? self
+        : {};
 var _document = _window.document;
 var _navigator = _window.navigator;
 
 function keepOriginalCallback(original, callback) {
   return isFunction(callback)
     ? function(data) {
         return callback(data, original);
       }
@@ -200,17 +206,17 @@ function Raven() {
  * @this {Raven}
  */
 
 Raven.prototype = {
   // Hardcode version string so that raven source can be loaded directly via
   // webpack (using a build step causes webpack #1617). Grunt verifies that
   // this value matches package.json during build.
   //   See: https://github.com/getsentry/raven-js/issues/465
-  VERSION: '3.24.1',
+  VERSION: '3.25.2',
 
   debug: false,
 
   TraceKit: TraceKit, // alias to TraceKit
 
   /*
      * Configure Raven with a DSN and extra options
      *
@@ -532,27 +538,45 @@ Raven.prototype = {
    * @return {Raven}
    */
   captureException: function(ex, options) {
     options = objectMerge({trimHeadFrames: 0}, options ? options : {});
 
     if (isErrorEvent(ex) && ex.error) {
       // If it is an ErrorEvent with `error` property, extract it to get actual Error
       ex = ex.error;
+    } else if (isDOMError(ex) || isDOMException(ex)) {
+      // If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers)
+      // then we just extract the name and message, as they don't provide anything else
+      // https://developer.mozilla.org/en-US/docs/Web/API/DOMError
+      // https://developer.mozilla.org/en-US/docs/Web/API/DOMException
+      var name = ex.name || (isDOMError(ex) ? 'DOMError' : 'DOMException');
+      var message = ex.message ? name + ': ' + ex.message : name;
+
+      return this.captureMessage(
+        message,
+        objectMerge(options, {
+          // neither DOMError or DOMException provide stack trace and we most likely wont get it this way as well
+          // but it's barely any overhead so we may at least try
+          stacktrace: true,
+          trimHeadFrames: options.trimHeadFrames + 1
+        })
+      );
     } else if (isError(ex)) {
       // we have a real Error object
       ex = ex;
     } else if (isPlainObject(ex)) {
       // If it is plain Object, serialize it manually and extract options
       // This will allow us to group events based on top-level keys
       // which is much better than creating new group when any key/value change
       options = this._getCaptureExceptionOptionsFromPlainObject(options, ex);
       ex = new Error(options.message);
     } else {
       // If none of previous checks were valid, then it means that
+      // it's not a DOMError/DOMException
       // it's not a plain Object
       // it's not a valid ErrorEvent (one with an error property)
       // it's not an Error
       // So bail out and capture it as a simple message:
       return this.captureMessage(
         ex,
         objectMerge(options, {
           stacktrace: true, // if we fall back to captureMessage, default to attempting a new trace
@@ -634,16 +658,24 @@ Raven.prototype = {
     }
 
     // null exception name so `Error` isn't prefixed to msg
     ex.name = null;
     var stack = TraceKit.computeStackTrace(ex);
 
     // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1]
     var initialCall = isArray(stack.stack) && stack.stack[1];
+
+    // if stack[1] is `Raven.captureException`, it means that someone passed a string to it and we redirected that call
+    // to be handled by `captureMessage`, thus `initialCall` is the 3rd one, not 2nd
+    // initialCall => captureException(string) => captureMessage(string)
+    if (initialCall && initialCall.func === 'Raven.captureException') {
+      initialCall = stack.stack[2];
+    }
+
     var fileurl = (initialCall && initialCall.url) || '';
 
     if (
       !!this._globalOptions.ignoreUrls.test &&
       this._globalOptions.ignoreUrls.test(fileurl)
     ) {
       return;
     }
@@ -1431,57 +1463,70 @@ Raven.prototype = {
             }
 
             var fetchData = {
               method: method,
               url: url,
               status_code: null
             };
 
-            return origFetch.apply(this, args).then(function(response) {
-              fetchData.status_code = response.status;
-
-              self.captureBreadcrumb({
-                type: 'http',
-                category: 'fetch',
-                data: fetchData
+            return origFetch
+              .apply(this, args)
+              .then(function(response) {
+                fetchData.status_code = response.status;
+
+                self.captureBreadcrumb({
+                  type: 'http',
+                  category: 'fetch',
+                  data: fetchData
+                });
+
+                return response;
+              })
+              ['catch'](function(err) {
+                // if there is an error performing the request
+                self.captureBreadcrumb({
+                  type: 'http',
+                  category: 'fetch',
+                  data: fetchData,
+                  level: 'error'
+                });
+
+                throw err;
               });
-
-              return response;
-            });
           };
         },
         wrappedBuiltIns
       );
     }
 
     // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
     // to the document. Do this before we instrument addEventListener.
     if (autoBreadcrumbs.dom && this._hasDocument) {
       if (_document.addEventListener) {
         _document.addEventListener('click', self._breadcrumbEventHandler('click'), false);
         _document.addEventListener('keypress', self._keypressEventHandler(), false);
-      } else if(_document.attachEvent){
+      } else if (_document.attachEvent) {
         // IE8 Compatibility
         _document.attachEvent('onclick', self._breadcrumbEventHandler('click'));
         _document.attachEvent('onkeypress', self._keypressEventHandler());
       }
     }
 
     // record navigation (URL) changes
     // NOTE: in Chrome App environment, touching history.pushState, *even inside
     //       a try/catch block*, will cause Chrome to output an error to console.error
     // borrowed from: https://github.com/angular/angular.js/pull/13945/files
     var chrome = _window.chrome;
     var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime;
     var hasPushAndReplaceState =
       !isChromePackagedApp &&
       _window.history &&
-      history.pushState &&
-      history.replaceState;
+      _window.history.pushState &&
+      _window.history.replaceState;
     if (autoBreadcrumbs.location && hasPushAndReplaceState) {
       // TODO: remove onpopstate handler on uninstall()
       var oldOnPopState = _window.onpopstate;
       _window.onpopstate = function() {
         var currentHref = self._location.href;
         self._captureUrlChange(self._lastHref, currentHref);
 
         if (oldOnPopState) {
@@ -1500,18 +1545,18 @@ Raven.prototype = {
             // coerce to string (this is what pushState does)
             self._captureUrlChange(self._lastHref, url + '');
           }
 
           return origHistFunction.apply(this, arguments);
         };
       };
 
-      fill(history, 'pushState', historyReplacementFunction, wrappedBuiltIns);
-      fill(history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);
+      fill(_window.history, 'pushState', historyReplacementFunction, wrappedBuiltIns);
+      fill(_window.history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);
     }
 
     if (autoBreadcrumbs.console && 'console' in _window && console.log) {
       // console
       var consoleMethodCallback = function(msg, data) {
         self.captureBreadcrumb({
           message: msg,
           level: data.level,
@@ -1717,17 +1762,17 @@ Raven.prototype = {
           values: [
             {
               type: type,
               value: message,
               stacktrace: stacktrace
             }
           ]
         },
-        culprit: fileurl
+        transaction: fileurl
       },
       options
     );
 
     // Fire away!
     this._send(data);
   },
 
@@ -1791,17 +1836,17 @@ Raven.prototype = {
   },
 
   _getHttpData: function() {
     if (!this._hasNavigator && !this._hasDocument) return;
     var httpData = {};
 
     if (this._hasNavigator && _navigator.userAgent) {
       httpData.headers = {
-        'User-Agent': navigator.userAgent
+        'User-Agent': _navigator.userAgent
       };
     }
 
     // Check in `window` instead of `document`, as we may be in ServiceWorker environment
     if (_window.location && _window.location.href) {
       httpData.url = _window.location.href;
     }
 
@@ -1832,17 +1877,17 @@ Raven.prototype = {
    *       data object with a single frame (derived from the onerror args).
    */
   _isRepeatData: function(current) {
     var last = this._lastData;
 
     if (
       !last ||
       current.message !== last.message || // defined for captureMessage
-      current.culprit !== last.culprit // defined for captureException/onerror
+      current.transaction !== last.transaction // defined for captureException/onerror
     )
       return false;
 
     // Stacktrace interface (i.e. from captureMessage)
     if (current.stacktrace || last.stacktrace) {
       return isSameStacktrace(current.stacktrace, last.stacktrace);
     } else if (current.exception || last.exception) {
       // Exception interface (i.e. from captureException/onerror)
@@ -2177,17 +2222,21 @@ Raven.prototype = {
         evaluated[key] = typeof value === 'function' ? value() : value;
       }
     }
 
     return evaluated;
   },
 
   _logDebug: function(level) {
-    if (this._originalConsoleMethods[level] && this.debug) {
+    // We allow `Raven.debug` and `Raven.config(DSN, { debug: true })` to not make backward incompatible API change
+    if (
+      this._originalConsoleMethods[level] &&
+      (this.debug || this._globalOptions.debug)
+    ) {
       // In IE<10 console methods do not have their own 'apply' method
       Function.prototype.apply.call(
         this._originalConsoleMethods[level],
         this._originalConsole,
         [].slice.call(arguments, 1)
       );
     }
   },
@@ -2290,30 +2339,38 @@ var _window =
 
 function isObject(what) {
   return typeof what === 'object' && what !== null;
 }
 
 // Yanked from https://git.io/vS8DV re-used under CC0
 // with some tiny modifications
 function isError(value) {
-  switch ({}.toString.call(value)) {
+  switch (Object.prototype.toString.call(value)) {
     case '[object Error]':
       return true;
     case '[object Exception]':
       return true;
     case '[object DOMException]':
       return true;
     default:
       return value instanceof Error;
   }
 }
 
 function isErrorEvent(value) {
-  return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]';
+  return Object.prototype.toString.call(value) === '[object ErrorEvent]';
+}
+
+function isDOMError(value) {
+  return Object.prototype.toString.call(value) === '[object DOMError]';
+}
+
+function isDOMException(value) {
+  return Object.prototype.toString.call(value) === '[object DOMException]';
 }
 
 function isUndefined(what) {
   return what === void 0;
 }
 
 function isFunction(what) {
   return typeof what === 'function';
@@ -2346,16 +2403,34 @@ function supportsErrorEvent() {
   try {
     new ErrorEvent(''); // eslint-disable-line no-new
     return true;
   } catch (e) {
     return false;
   }
 }
 
+function supportsDOMError() {
+  try {
+    new DOMError(''); // eslint-disable-line no-new
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function supportsDOMException() {
+  try {
+    new DOMException(''); // eslint-disable-line no-new
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
 function supportsFetch() {
   if (!('fetch' in _window)) return false;
 
   try {
     new Headers(); // eslint-disable-line no-new
     new Request(''); // eslint-disable-line no-new
     new Response(); // eslint-disable-line no-new
     return true;
@@ -2438,17 +2513,23 @@ function objectMerge(obj1, obj2) {
 function objectFrozen(obj) {
   if (!Object.isFrozen) {
     return false;
   }
   return Object.isFrozen(obj);
 }
 
 function truncate(str, max) {
-  return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
+  if (typeof max !== 'number') {
+    throw new Error('2nd argument to `truncate` function should be a number');
+  }
+  if (typeof str !== 'string' || max === 0) {
+    return str;
+  }
+  return str.length <= max ? str : str.substr(0, max) + '\u2026';
 }
 
 /**
  * hasKey, a better form of hasOwnProperty
  * Example: hasKey(MainHostObject, property) === true/false
  *
  * @param {Object} host object to check property
  * @param {string} key to check
@@ -2737,20 +2818,19 @@ function utf8Length(value) {
   return ~-encodeURI(value).split(/%..|./).length;
 }
 
 function jsonSize(value) {
   return utf8Length(JSON.stringify(value));
 }
 
 function serializeValue(value) {
-  var maxLength = 40;
-
   if (typeof value === 'string') {
-    return value.length <= maxLength ? value : value.substr(0, maxLength - 1) + '\u2026';
+    var maxLength = 40;
+    return truncate(value, maxLength);
   } else if (
     typeof value === 'number' ||
     typeof value === 'boolean' ||
     typeof value === 'undefined'
   ) {
     return value;
   }
 
@@ -2856,23 +2936,27 @@ function sanitize(input, sanitizeKeys) {
 
   return sanitizeWorker(safeInput);
 }
 
 module.exports = {
   isObject: isObject,
   isError: isError,
   isErrorEvent: isErrorEvent,
+  isDOMError: isDOMError,
+  isDOMException: isDOMException,
   isUndefined: isUndefined,
   isFunction: isFunction,
   isPlainObject: isPlainObject,
   isString: isString,
   isArray: isArray,
   isEmptyObject: isEmptyObject,
   supportsErrorEvent: supportsErrorEvent,
+  supportsDOMError: supportsDOMError,
+  supportsDOMException: supportsDOMException,
   supportsFetch: supportsFetch,
   supportsReferrerPolicy: supportsReferrerPolicy,
   supportsPromiseRejectionEvent: supportsPromiseRejectionEvent,
   wrappedCallback: wrappedCallback,
   each: each,
   objectMerge: objectMerge,
   truncate: truncate,
   objectFrozen: objectFrozen,
@@ -2922,20 +3006,34 @@ var _window =
 var _slice = [].slice;
 var UNKNOWN_FUNCTION = '?';
 
 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
 var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;
 
 function getLocationHref() {
   if (typeof document === 'undefined' || document.location == null) return '';
-
   return document.location.href;
 }
 
+function getLocationOrigin() {
+  if (typeof document === 'undefined' || document.location == null) return '';
+
+  // Oh dear IE10...
+  if (!document.location.origin) {
+    document.location.origin =
+      document.location.protocol +
+      '//' +
+      document.location.hostname +
+      (document.location.port ? ':' + document.location.port : '');
+  }
+
+  return document.location.origin;
+}
+
 /**
  * TraceKit.report: cross-browser processing of unhandled exceptions
  *
  * Syntax:
  *   TraceKit.report.subscribe(function(stackInfo) { ... })
  *   TraceKit.report.unsubscribe(function(stackInfo) { ... })
  *   TraceKit.report(exception)
  *   try { ...code... } catch(ex) { TraceKit.report(ex); }
@@ -3333,16 +3431,54 @@ TraceKit.computeStackTrace = (function c
       } else {
         continue;
       }
 
       if (!element.func && element.line) {
         element.func = UNKNOWN_FUNCTION;
       }
 
+      if (element.url && element.url.substr(0, 5) === 'blob:') {
+        // Special case for handling JavaScript loaded into a blob.
+        // We use a synchronous AJAX request here as a blob is already in
+        // memory - it's not making a network request.  This will generate a warning
+        // in the browser console, but there has already been an error so that's not
+        // that much of an issue.
+        var xhr = new XMLHttpRequest();
+        xhr.open('GET', element.url, false);
+        xhr.send(null);
+
+        // If we failed to download the source, skip this patch
+        if (xhr.status === 200) {
+          var source = xhr.responseText || '';
+
+          // We trim the source down to the last 300 characters as sourceMappingURL is always at the end of the file.
+          // Why 300? To be in line with: https://github.com/getsentry/sentry/blob/4af29e8f2350e20c28a6933354e4f42437b4ba42/src/sentry/lang/javascript/processor.py#L164-L175
+          source = source.slice(-300);
+
+          // Now we dig out the source map URL
+          var sourceMaps = source.match(/\/\/# sourceMappingURL=(.*)$/);
+
+          // If we don't find a source map comment or we find more than one, continue on to the next element.
+          if (sourceMaps) {
+            var sourceMapAddress = sourceMaps[1];
+
+            // Now we check to see if it's a relative URL.
+            // If it is, convert it to an absolute one.
+            if (sourceMapAddress.charAt(0) === '~') {
+              sourceMapAddress = getLocationOrigin() + sourceMapAddress.slice(1);
+            }
+
+            // Now we strip the '.map' off of the end of the URL and update the
+            // element so that Sentry can match the map to the blob.
+            element.url = sourceMapAddress.slice(0, -4);
+          }
+        }
+      }
+
       stack.push(element);
     }
 
     if (!stack.length) {
       return null;
     }
 
     return {