Bug 1291877 - Source map optimization in console due to Unique Locations. r=jsantell
authorJaideep Bhoosreddy <jaideepb@buffalo.edu>
Tue, 16 Aug 2016 23:21:00 -0400
changeset 310059 87d16ac231613bb4b5a40bdade08659936940634
parent 310058 165ca35b158600ef4b31d4f26a78fdbaa53828d5
child 310060 27d5ebc65560febb4e7baf7f2bcc79cb373e300e
push id80771
push userkwierso@gmail.com
push dateThu, 18 Aug 2016 23:33:08 +0000
treeherdermozilla-inbound@cb1295738c37 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell
bugs1291877
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1291877 - Source map optimization in console due to Unique Locations. r=jsantell
devtools/client/framework/source-map-service.js
--- a/devtools/client/framework/source-map-service.js
+++ b/devtools/client/framework/source-map-service.js
@@ -14,16 +14,17 @@ const { LocationStore, serialize, deseri
  * auto-update when the source changes (from pretty printing, source maps loading, etc)
  *
  * @param {TabTarget} target
  */
 
 function SourceMapService(target) {
   this._target = target;
   this._locationStore = new LocationStore();
+  this._isNotSourceMapped = new Map();
 
   EventEmitter.decorate(this);
 
   this._onSourceUpdated = this._onSourceUpdated.bind(this);
   this._resolveLocation = this._resolveLocation.bind(this);
   this._resolveAndUpdate = this._resolveAndUpdate.bind(this);
   this.subscribe = this.subscribe.bind(this);
   this.unsubscribe = this.unsubscribe.bind(this);
@@ -32,37 +33,47 @@ function SourceMapService(target) {
 
   target.on("source-updated", this._onSourceUpdated);
   target.on("navigate", this.reset);
   target.on("will-navigate", this.reset);
   target.on("close", this.destroy);
 }
 
 /**
- * Clears the store containing the cached resolved locations and promises
+ * Clears the store containing the cached promised locations
  */
 SourceMapService.prototype.reset = function () {
   this._locationStore.clear();
+  this._isNotSourceMapped.clear();
 };
 
 SourceMapService.prototype.destroy = function () {
   this.reset();
   this._target.off("source-updated", this._onSourceUpdated);
   this._target.off("navigate", this.reset);
   this._target.off("will-navigate", this.reset);
   this._target.off("close", this.destroy);
-  this._target = this._locationStore = null;
+  this._target = this._locationStore = this._isNotSourceMapped = null;
 };
 
 /**
- * Sets up listener for the callback to update the FrameView and tries to resolve location
+ * Sets up listener for the callback to update the FrameView
+ * and tries to resolve location, if it is source-mappable
  * @param location
  * @param callback
  */
 SourceMapService.prototype.subscribe = function (location, callback) {
+  // A valid candidate location for source-mapping should have a url and line.
+  // Abort if there's no `url`, which means it's unsourcemappable anyway,
+  // like an eval script.
+  // From previous attempts to source-map locations, we also determine if a location
+  // is not source-mapped.
+  if (!location.url || !location.line || this._isNotSourceMapped.get(location.url)) {
+    return;
+  }
   this.on(serialize(location), callback);
   this._locationStore.set(location);
   this._resolveAndUpdate(location);
 };
 
 /**
  * Removes the listener for the location and clears cached locations
  * @param location
@@ -75,72 +86,74 @@ SourceMapService.prototype.unsubscribe =
   // condition is to protect against that. Could be looked into in the future.
   if (this._locationStore) {
     this._locationStore.clearByURL(location.url);
   }
 };
 
 /**
  * Tries to resolve the location and if successful,
- * emits the resolved location and caches it
+ * emits the resolved location
  * @param location
  * @private
  */
 SourceMapService.prototype._resolveAndUpdate = function (location) {
   this._resolveLocation(location).then(resolvedLocation => {
     // We try to source map the first console log to initiate the source-updated
     // event from target. The isSameLocation check is to make sure we don't update
     // the frame, if the location is not source-mapped.
     if (resolvedLocation && !isSameLocation(location, resolvedLocation)) {
       this.emit(serialize(location), location, resolvedLocation);
     }
   });
 };
 
 /**
- * Validates the location model,
- * checks if there is existing promise to resolve location, if so returns cached promise
- * if not promised to resolve,
- * tries to resolve location and returns a promised location
+ * Checks if there is existing promise to resolve location, if so returns cached promise
+ * if not, tries to resolve location and returns a promised location
  * @param location
  * @return Promise<Object>
  * @private
  */
 SourceMapService.prototype._resolveLocation = Task.async(function* (location) {
-  // Location must have a url and a line
-  if (!location.url || !location.line) {
-    return null;
-  }
+  let resolvedLocation;
   const cachedLocation = this._locationStore.get(location);
   if (cachedLocation) {
-    return cachedLocation;
+    resolvedLocation = cachedLocation;
   } else {
     const promisedLocation = resolveLocation(this._target, location);
     if (promisedLocation) {
       this._locationStore.set(location, promisedLocation);
-      return promisedLocation;
+      resolvedLocation = promisedLocation;
     }
   }
+  return resolvedLocation;
 });
 
 /**
  * Checks if the `source-updated` event is fired from the target.
  * Checks to see if location store has the source url in its cache,
  * if so, tries to update each stale location in the store.
+ * Determines if the source should be source-mapped or not.
  * @param _
  * @param sourceEvent
  * @private
  */
 SourceMapService.prototype._onSourceUpdated = function (_, sourceEvent) {
   let { type, source } = sourceEvent;
+
   // If we get a new source, and it's not a source map, abort;
   // we can have no actionable updates as this is just a new normal source.
-  // Also abort if there's no `url`, which means it's unsourcemappable anyway,
-  // like an eval script.
-  if (!source.url || type === "newSource" && !source.isSourceMapped) {
+  // Check Source Actor for sourceMapURL property (after Firefox 48)
+  // If not present, utilize isSourceMapped and isPrettyPrinted properties
+  // to estimate if a source is not source-mapped.
+  const isNotSourceMapped = !(source.sourceMapURL ||
+    source.isSourceMapped || source.isPrettyPrinted);
+  if (type === "newSource" && isNotSourceMapped) {
+    this._isNotSourceMapped.set(source.url, true);
     return;
   }
   let sourceUrl = null;
   if (source.generatedUrl && source.isSourceMapped) {
     sourceUrl = source.generatedUrl;
   } else if (source.url && source.isPrettyPrinted) {
     sourceUrl = source.url;
   }
@@ -175,17 +188,17 @@ function resolveLocation(target, locatio
     if (newLocation.error) {
       return null;
     }
     return newLocation;
   });
 }
 
 /**
- * Returns if the original location and resolved location are the same
+ * Returns true if the original location and resolved location are the same
  * @param location
  * @param resolvedLocation
  * @returns {boolean}
  */
 function isSameLocation(location, resolvedLocation) {
   return location.url === resolvedLocation.url &&
     location.line === resolvedLocation.line &&
     location.column === resolvedLocation.column;