mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 17:55:50 +00:00
Bug 670002 - Use source maps in the web console w/ performance issues. r=jsantell
--HG-- rename : devtools/client/framework/source-location.js => devtools/client/framework/source-map-service.js
This commit is contained in:
parent
cbe2855b89
commit
8f73e01607
103
devtools/client/framework/location-store.js
Normal file
103
devtools/client/framework/location-store.js
Normal file
@ -0,0 +1,103 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const SOURCE_TOKEN = "<:>";
|
||||
|
||||
function LocationStore (store) {
|
||||
this._store = store || new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a promised location from the Store.
|
||||
* @param location
|
||||
* @returns Promise<Object>
|
||||
*/
|
||||
LocationStore.prototype.get = function (location) {
|
||||
this._safeAccessInit(location.url);
|
||||
return this._store.get(location.url).get(location);
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to set a promised location to the Store
|
||||
* @param location
|
||||
* @param promisedLocation
|
||||
*/
|
||||
LocationStore.prototype.set = function (location, promisedLocation = null) {
|
||||
this._safeAccessInit(location.url);
|
||||
this._store.get(location.url).set(serialize(location), promisedLocation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility method to verify if key exists in Store before accessing it.
|
||||
* If not, initializing it.
|
||||
* @param url
|
||||
* @private
|
||||
*/
|
||||
LocationStore.prototype._safeAccessInit = function (url) {
|
||||
if (!this._store.has(url)) {
|
||||
this._store.set(url, new Map());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility proxy method to Map.clear() method
|
||||
*/
|
||||
LocationStore.prototype.clear = function () {
|
||||
this._store.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves an object containing all locations to be resolved when `source-updated`
|
||||
* event is triggered.
|
||||
* @param url
|
||||
* @returns {Array<String>}
|
||||
*/
|
||||
LocationStore.prototype.getByURL = function (url){
|
||||
if (this._store.has(url)) {
|
||||
return [...this._store.get(url).keys()];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Invalidates the stale location promises from the store when `source-updated`
|
||||
* event is triggered, and when FrameView unsubscribes from a location.
|
||||
* @param url
|
||||
*/
|
||||
LocationStore.prototype.clearByURL = function (url) {
|
||||
this._safeAccessInit(url);
|
||||
this._store.set(url, new Map());
|
||||
};
|
||||
|
||||
exports.LocationStore = LocationStore;
|
||||
exports.serialize = serialize;
|
||||
exports.deserialize = deserialize;
|
||||
|
||||
/**
|
||||
* Utility method to serialize the source
|
||||
* @param source
|
||||
* @returns {string}
|
||||
*/
|
||||
function serialize(source) {
|
||||
let { url, line, column } = source;
|
||||
line = line || 0;
|
||||
column = column || 0;
|
||||
return `${url}${SOURCE_TOKEN}${line}${SOURCE_TOKEN}${column}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility method to serialize the source
|
||||
* @param source
|
||||
* @returns Object
|
||||
*/
|
||||
function deserialize(source) {
|
||||
let [ url, line, column ] = source.split(SOURCE_TOKEN);
|
||||
line = parseInt(line);
|
||||
column = parseInt(column);
|
||||
if (column === 0) {
|
||||
return { url, line };
|
||||
}
|
||||
return { url, line, column };
|
||||
};
|
@ -16,11 +16,12 @@ DevToolsModules(
|
||||
'devtools-browser.js',
|
||||
'devtools.js',
|
||||
'gDevTools.jsm',
|
||||
'location-store.js',
|
||||
'menu-item.js',
|
||||
'menu.js',
|
||||
'selection.js',
|
||||
'sidebar.js',
|
||||
'source-location.js',
|
||||
'source-map-service.js',
|
||||
'target-from-url.js',
|
||||
'target.js',
|
||||
'toolbox-highlighter-utils.js',
|
||||
|
@ -1,137 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
/**
|
||||
* A manager class that wraps a TabTarget and listens to source changes
|
||||
* from source maps and resolves non-source mapped locations to the source mapped
|
||||
* versions and back and forth, and creating smart elements with a location that
|
||||
* auto-update when the source changes (from pretty printing, source maps loading, etc)
|
||||
*
|
||||
* @param {TabTarget} target
|
||||
*/
|
||||
function SourceLocationController(target) {
|
||||
this.target = target;
|
||||
this.locations = new Set();
|
||||
|
||||
this._onSourceUpdated = this._onSourceUpdated.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
|
||||
target.on("source-updated", this._onSourceUpdated);
|
||||
target.on("navigate", this.reset);
|
||||
target.on("will-navigate", this.reset);
|
||||
target.on("close", this.destroy);
|
||||
}
|
||||
|
||||
SourceLocationController.prototype.reset = function () {
|
||||
this.locations.clear();
|
||||
};
|
||||
|
||||
SourceLocationController.prototype.destroy = function () {
|
||||
this.locations.clear();
|
||||
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.locations = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add this `location` to be observed and register a callback
|
||||
* whenever the underlying source is updated.
|
||||
*
|
||||
* @param {Object} location
|
||||
* An object with a {String} url, {Number} line, and optionally
|
||||
* a {Number} column.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
SourceLocationController.prototype.bindLocation = function (location, callback) {
|
||||
assert(location.url, "Location must have a url.");
|
||||
assert(location.line, "Location must have a line.");
|
||||
this.locations.add({ location, callback });
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a new source occurs (a normal source, source maps) or an updated
|
||||
* source (pretty print) occurs.
|
||||
*
|
||||
* @param {String} eventName
|
||||
* @param {Object} sourceEvent
|
||||
*/
|
||||
SourceLocationController.prototype._onSourceUpdated = function (_, sourceEvent) {
|
||||
let { type, source } = sourceEvent;
|
||||
// If we get a new source, and it's not a source map, abort;
|
||||
// we can ahve 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let locationItem of this.locations) {
|
||||
if (isSourceRelated(locationItem.location, source)) {
|
||||
this._updateSource(locationItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SourceLocationController.prototype._updateSource = Task.async(function* (locationItem) {
|
||||
let newLocation = yield resolveLocation(this.target, locationItem.location);
|
||||
if (newLocation) {
|
||||
let previousLocation = Object.assign({}, locationItem.location);
|
||||
Object.assign(locationItem.location, newLocation);
|
||||
locationItem.callback(previousLocation, newLocation);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Take a TabTarget and a location, containing a `url`, `line`, and `column`, resolve
|
||||
* the location to the latest location (so a source mapped location, or if pretty print
|
||||
* status has been updated)
|
||||
*
|
||||
* @param {TabTarget} target
|
||||
* @param {Object} location
|
||||
* @return {Promise<Object>}
|
||||
*/
|
||||
function resolveLocation(target, location) {
|
||||
return Task.spawn(function* () {
|
||||
let newLocation = yield target.resolveLocation({
|
||||
url: location.url,
|
||||
line: location.line,
|
||||
column: location.column || Infinity
|
||||
});
|
||||
|
||||
// Source or mapping not found, so don't do anything
|
||||
if (newLocation.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return newLocation;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a serialized SourceActor form and returns a boolean indicating
|
||||
* if this source is related to this location, like if a location is a generated source,
|
||||
* and the source map is loaded subsequently, the new source mapped SourceActor
|
||||
* will be considered related to this location. Same with pretty printing new sources.
|
||||
*
|
||||
* @param {Object} location
|
||||
* @param {Object} source
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function isSourceRelated(location, source) {
|
||||
// Mapping location to subsequently loaded source map
|
||||
return source.generatedUrl === location.url ||
|
||||
// Mapping source map loc to source map
|
||||
source.url === location.url;
|
||||
}
|
||||
|
||||
exports.SourceLocationController = SourceLocationController;
|
||||
exports.resolveLocation = resolveLocation;
|
||||
exports.isSourceRelated = isSourceRelated;
|
200
devtools/client/framework/source-map-service.js
Normal file
200
devtools/client/framework/source-map-service.js
Normal file
@ -0,0 +1,200 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { LocationStore, serialize, deserialize } = require("./location-store");
|
||||
|
||||
/**
|
||||
* A manager class that wraps a TabTarget and listens to source changes
|
||||
* from source maps and resolves non-source mapped locations to the source mapped
|
||||
* versions and back and forth, and creating smart elements with a location that
|
||||
* 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._isInitialResolve = true;
|
||||
|
||||
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);
|
||||
this.reset = this.reset.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
|
||||
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
|
||||
*/
|
||||
SourceMapService.prototype.reset = function () {
|
||||
this._isInitialResolve = true;
|
||||
this._locationStore.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._isInitialResolve = null;
|
||||
this._target = this._locationStore = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up listener for the callback to update the FrameView and tries to resolve location
|
||||
* @param location
|
||||
* @param callback
|
||||
*/
|
||||
SourceMapService.prototype.subscribe = function (location, callback) {
|
||||
this.on(serialize(location), callback);
|
||||
this._locationStore.set(location);
|
||||
if (this._isInitialResolve) {
|
||||
this._resolveAndUpdate(location);
|
||||
this._isInitialResolve = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the listener for the location and clears cached locations
|
||||
* @param location
|
||||
* @param callback
|
||||
*/
|
||||
SourceMapService.prototype.unsubscribe = function (location, callback) {
|
||||
this.off(serialize(location), callback);
|
||||
this._locationStore.clearByURL(location.url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tries to resolve the location and if successful,
|
||||
* emits the resolved location and caches it
|
||||
* @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) {
|
||||
if (this._isInitialResolve) {
|
||||
if (!isSameLocation(location, resolvedLocation)) {
|
||||
this.emit(serialize(location), location, resolvedLocation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
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
|
||||
* @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;
|
||||
}
|
||||
const cachedLocation = this._locationStore.get(location);
|
||||
if (cachedLocation) {
|
||||
return cachedLocation;
|
||||
} else {
|
||||
const promisedLocation = resolveLocation(this._target, location);
|
||||
if (promisedLocation) {
|
||||
this._locationStore.set(location, promisedLocation);
|
||||
return promisedLocation;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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) {
|
||||
return;
|
||||
}
|
||||
let sourceUrl = null;
|
||||
if (source.generatedUrl && source.isSourceMapped) {
|
||||
sourceUrl = source.generatedUrl;
|
||||
} else if (source.url && source.isPrettyPrinted) {
|
||||
sourceUrl = source.url;
|
||||
}
|
||||
const locationsToResolve = this._locationStore.getByURL(sourceUrl);
|
||||
if (locationsToResolve.length) {
|
||||
this._locationStore.clearByURL(sourceUrl);
|
||||
for (let location of locationsToResolve) {
|
||||
this._resolveAndUpdate(deserialize(location));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.SourceMapService = SourceMapService;
|
||||
|
||||
/**
|
||||
* Take a TabTarget and a location, containing a `url`, `line`, and `column`, resolve
|
||||
* the location to the latest location (so a source mapped location, or if pretty print
|
||||
* status has been updated)
|
||||
*
|
||||
* @param {TabTarget} target
|
||||
* @param {Object} location
|
||||
* @return {Promise<Object>}
|
||||
*/
|
||||
function resolveLocation(target, location) {
|
||||
return Task.spawn(function* () {
|
||||
let newLocation = yield target.resolveLocation({
|
||||
url: location.url,
|
||||
line: location.line,
|
||||
column: location.column || Infinity
|
||||
});
|
||||
// Source or mapping not found, so don't do anything
|
||||
if (newLocation.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return newLocation;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 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;
|
||||
};
|
@ -11,6 +11,7 @@ const OS_HISTOGRAM = "DEVTOOLS_OS_ENUMERATED_PER_USER";
|
||||
const OS_IS_64_BITS = "DEVTOOLS_OS_IS_64_BITS_PER_USER";
|
||||
const SCREENSIZE_HISTOGRAM = "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER";
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const { SourceMapService } = require("./source-map-service");
|
||||
|
||||
var {Cc, Ci, Cu} = require("chrome");
|
||||
var promise = require("promise");
|
||||
@ -118,6 +119,9 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._target = target;
|
||||
this._toolPanels = new Map();
|
||||
this._telemetry = new Telemetry();
|
||||
if (Services.prefs.getBoolPref("devtools.sourcemap.locations.enabled")) {
|
||||
this._sourceMapService = new SourceMapService(this._target);
|
||||
}
|
||||
|
||||
this._initInspector = null;
|
||||
this._inspector = null;
|
||||
@ -2032,6 +2036,11 @@ Toolbox.prototype = {
|
||||
|
||||
this._lastFocusedElement = null;
|
||||
|
||||
if (this._sourceMapService) {
|
||||
this._sourceMapService.destroy();
|
||||
this._sourceMapService = null;
|
||||
}
|
||||
|
||||
if (this.webconsolePanel) {
|
||||
this._saveSplitConsoleHeight();
|
||||
this.webconsolePanel.removeEventListener("resize",
|
||||
|
@ -296,6 +296,9 @@ pref("devtools.webconsole.autoMultiline", true);
|
||||
// Enable the experimental webconsole frontend (work in progress)
|
||||
pref("devtools.webconsole.new-frontend-enabled", false);
|
||||
|
||||
// Enable the experimental support for source maps in console (work in progress)
|
||||
pref("devtools.sourcemap.locations.enabled", false);
|
||||
|
||||
// The number of lines that are displayed in the web console.
|
||||
pref("devtools.hud.loglimit", 1000);
|
||||
|
||||
|
@ -34,6 +34,8 @@ module.exports = createClass({
|
||||
showEmptyPathAsHost: PropTypes.bool,
|
||||
// Option to display a full source instead of just the filename.
|
||||
showFullSourceUrl: PropTypes.bool,
|
||||
// Service to enable the source map feature for console.
|
||||
sourceMapService: PropTypes.object,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -46,10 +48,71 @@ module.exports = createClass({
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
const sourceMapService = this.props.sourceMapService;
|
||||
if (sourceMapService) {
|
||||
const source = this.getSource();
|
||||
sourceMapService.subscribe(source, this.onSourceUpdated);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
const sourceMapService = this.props.sourceMapService;
|
||||
if (sourceMapService) {
|
||||
const source = this.getSource();
|
||||
sourceMapService.unsubscribe(source, this.onSourceUpdated);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Component method to update the FrameView when a resolved location is available
|
||||
* @param event
|
||||
* @param location
|
||||
*/
|
||||
onSourceUpdated(event, location, resolvedLocation) {
|
||||
const frame = this.getFrame(resolvedLocation);
|
||||
this.setState({
|
||||
frame,
|
||||
isSourceMapped: true,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility method to convert the Frame object to the
|
||||
* Source Object model required by SourceMapService
|
||||
* @param frame
|
||||
* @returns {{url: *, line: *, column: *}}
|
||||
*/
|
||||
getSource(frame) {
|
||||
frame = frame || this.props.frame;
|
||||
const { source, line, column } = frame;
|
||||
return {
|
||||
url: source,
|
||||
line,
|
||||
column,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility method to convert the Source object model to the
|
||||
* Frame object model required by FrameView class.
|
||||
* @param source
|
||||
* @returns {{source: *, line: *, column: *, functionDisplayName: *}}
|
||||
*/
|
||||
getFrame(source) {
|
||||
const { url, line, column } = source;
|
||||
return {
|
||||
source: url,
|
||||
line,
|
||||
column,
|
||||
functionDisplayName: this.props.frame.functionDisplayName,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
let frame, isSourceMapped;
|
||||
let {
|
||||
onClick,
|
||||
frame,
|
||||
showFunctionName,
|
||||
showAnonymousFunctionName,
|
||||
showHost,
|
||||
@ -57,6 +120,13 @@ module.exports = createClass({
|
||||
showFullSourceUrl
|
||||
} = this.props;
|
||||
|
||||
if (this.state && this.state.isSourceMapped) {
|
||||
frame = this.state.frame;
|
||||
isSourceMapped = this.state.isSourceMapped;
|
||||
} else {
|
||||
frame = this.props.frame;
|
||||
}
|
||||
|
||||
let source = frame.source ? String(frame.source) : "";
|
||||
let line = frame.line != void 0 ? Number(frame.line) : null;
|
||||
let column = frame.column != void 0 ? Number(frame.column) : null;
|
||||
@ -66,17 +136,23 @@ module.exports = createClass({
|
||||
// has already cached this indirectly. We don't want to attempt to
|
||||
// link to "self-hosted" and "(unknown)". However, we do want to link
|
||||
// to Scratchpad URIs.
|
||||
const isLinkable = !!(isScratchpadScheme(source) || parseURL(source));
|
||||
// Source mapped sources might not necessary linkable, but they
|
||||
// are still valid in the debugger.
|
||||
const isLinkable = !!(isScratchpadScheme(source) || parseURL(source)) || isSourceMapped;
|
||||
const elements = [];
|
||||
const sourceElements = [];
|
||||
let sourceEl;
|
||||
|
||||
let tooltip = long;
|
||||
|
||||
// If the source is linkable and line > 0
|
||||
const shouldDisplayLine = isLinkable && line;
|
||||
|
||||
// Exclude all falsy values, including `0`, as even
|
||||
// a number 0 for line doesn't make sense, and should not be displayed.
|
||||
// If source isn't linkable, don't attempt to append line and column
|
||||
// info, as this probably doesn't make sense.
|
||||
if (isLinkable && line) {
|
||||
if (shouldDisplayLine) {
|
||||
tooltip += `:${line}`;
|
||||
// Intentionally exclude 0
|
||||
if (column) {
|
||||
@ -104,8 +180,17 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
let displaySource = showFullSourceUrl ? long : short;
|
||||
if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
|
||||
// SourceMapped locations might not be parsed properly by parseURL.
|
||||
// Eg: sourcemapped location could be /folder/file.coffee instead of a url
|
||||
// and so the url parser would not parse non-url locations properly
|
||||
// Check for "/" in displaySource. If "/" is in displaySource, take everything after last "/".
|
||||
if (isSourceMapped) {
|
||||
displaySource = displaySource.lastIndexOf("/") < 0 ?
|
||||
displaySource :
|
||||
displaySource.slice(displaySource.lastIndexOf("/") + 1);
|
||||
} else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
|
||||
displaySource = host;
|
||||
|
||||
}
|
||||
|
||||
sourceElements.push(dom.span({
|
||||
@ -113,7 +198,7 @@ module.exports = createClass({
|
||||
}, displaySource));
|
||||
|
||||
// If source is linkable, and we have a line number > 0
|
||||
if (isLinkable && line) {
|
||||
if (shouldDisplayLine) {
|
||||
let lineInfo = `:${line}`;
|
||||
// Add `data-line` attribute for testing
|
||||
attributes["data-line"] = line;
|
||||
@ -134,7 +219,7 @@ module.exports = createClass({
|
||||
sourceEl = dom.a({
|
||||
onClick: e => {
|
||||
e.preventDefault();
|
||||
onClick(frame);
|
||||
onClick(this.getSource(frame));
|
||||
},
|
||||
href: source,
|
||||
className: "frame-link-source",
|
||||
|
@ -2531,7 +2531,7 @@ WebConsoleFrame.prototype = {
|
||||
|
||||
let fullURL = url.split(" -> ").pop();
|
||||
// Make the location clickable.
|
||||
let onClick = () => {
|
||||
let onClick = ({ url, line }) => {
|
||||
let category = locationNode.closest(".message").category;
|
||||
let target = null;
|
||||
|
||||
@ -2541,10 +2541,14 @@ WebConsoleFrame.prototype = {
|
||||
target = "styleeditor";
|
||||
} else if (category === CATEGORY_JS || category === CATEGORY_WEBDEV) {
|
||||
target = "jsdebugger";
|
||||
} else if (/\.js$/.test(fullURL)) {
|
||||
} else if (/\.js$/.test(url)) {
|
||||
// If it ends in .js, let's attempt to open in debugger
|
||||
// anyway, as this falls back to normal view-source.
|
||||
target = "jsdebugger";
|
||||
} else {
|
||||
// Point everything else to debugger, if source not available,
|
||||
// it will fall back to view-source.
|
||||
target = "jsdebugger";
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
@ -2552,16 +2556,17 @@ WebConsoleFrame.prototype = {
|
||||
this.owner.viewSourceInScratchpad(url, line);
|
||||
return;
|
||||
case "jsdebugger":
|
||||
this.owner.viewSourceInDebugger(fullURL, line);
|
||||
this.owner.viewSourceInDebugger(url, line);
|
||||
return;
|
||||
case "styleeditor":
|
||||
this.owner.viewSourceInStyleEditor(fullURL, line);
|
||||
this.owner.viewSourceInStyleEditor(url, line);
|
||||
return;
|
||||
}
|
||||
// No matching tool found; use old school view-source
|
||||
this.owner.viewSource(fullURL, line);
|
||||
this.owner.viewSource(url, line);
|
||||
};
|
||||
|
||||
const toolbox = gDevTools.getToolbox(this.owner.target);
|
||||
this.ReactDOM.render(this.FrameView({
|
||||
frame: {
|
||||
source: fullURL,
|
||||
@ -2570,6 +2575,7 @@ WebConsoleFrame.prototype = {
|
||||
},
|
||||
showEmptyPathAsHost: true,
|
||||
onClick,
|
||||
sourceMapService: toolbox ? toolbox._sourceMapService : null,
|
||||
}), locationNode);
|
||||
|
||||
return locationNode;
|
||||
|
@ -245,7 +245,7 @@ TabSources.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("getSourceByURL: could not find source for " + url);
|
||||
throw new Error("getSourceActorByURL: could not find source for " + url);
|
||||
return null;
|
||||
},
|
||||
|
||||
|
@ -2091,41 +2091,37 @@ TabActor.prototype = {
|
||||
onResolveLocation(request) {
|
||||
let { url, line } = request;
|
||||
let column = request.column || 0;
|
||||
let actor = this.sources.getSourceActorByURL(url);
|
||||
const scripts = this.threadActor.dbg.findScripts({ url });
|
||||
|
||||
if (actor) {
|
||||
// Get the generated source actor if this is source mapped
|
||||
let generatedActor = actor.generatedSource ?
|
||||
this.sources.createNonSourceMappedActor(actor.generatedSource) :
|
||||
actor;
|
||||
let generatedLocation = new GeneratedLocation(
|
||||
generatedActor, line, column);
|
||||
|
||||
return this.sources.getOriginalLocation(generatedLocation).then(loc => {
|
||||
// If no map found, return this packet
|
||||
if (loc.originalLine == null) {
|
||||
return {
|
||||
from: this.actorID,
|
||||
type: "resolveLocation",
|
||||
error: "MAP_NOT_FOUND"
|
||||
};
|
||||
}
|
||||
|
||||
loc = loc.toJSON();
|
||||
return {
|
||||
from: this.actorID,
|
||||
url: loc.source.url,
|
||||
column: loc.column,
|
||||
line: loc.line
|
||||
};
|
||||
if (!scripts[0] || !scripts[0].source) {
|
||||
return promise.resolve({
|
||||
from: this.actorID,
|
||||
type: "resolveLocation",
|
||||
error: "SOURCE_NOT_FOUND"
|
||||
});
|
||||
}
|
||||
const source = scripts[0].source;
|
||||
const generatedActor = this.sources.createNonSourceMappedActor(source);
|
||||
let generatedLocation = new GeneratedLocation(
|
||||
generatedActor, line, column);
|
||||
|
||||
// Fall back to this packet when source is not found
|
||||
return promise.resolve({
|
||||
from: this.actorID,
|
||||
type: "resolveLocation",
|
||||
error: "SOURCE_NOT_FOUND"
|
||||
return this.sources.getOriginalLocation(generatedLocation).then(loc => {
|
||||
// If no map found, return this packet
|
||||
if (loc.originalLine == null) {
|
||||
return {
|
||||
from: this.actorID,
|
||||
type: "resolveLocation",
|
||||
error: "MAP_NOT_FOUND"
|
||||
};
|
||||
}
|
||||
|
||||
loc = loc.toJSON();
|
||||
return {
|
||||
from: this.actorID,
|
||||
url: loc.source.url,
|
||||
column: loc.column,
|
||||
line: loc.line
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user