devil-windows
Version:
Debugger, profiler and runtime with embedded WebKit DevTools client (for Windows).
249 lines (209 loc) • 7.23 kB
JavaScript
// see Blink inspector > ContentSearchUtils.cpp > findMagicComment()
var SOURCE_MAP_URL_REGEX = /\/\/[@#][ \t]sourceMappingURL=[ \t]*([^\s'"]*)[ \t]*$/m;
/**
* ScriptManager class. Uses a v8.Debugger
* Refactored from node-inspector
* https://github.com/node-inspector/node-inspector
*
* @param {Array.<string>} hidden
* @param {Debugger} debuggerClient
* @constructor v8.ScriptManager
*/
function ScriptManager (debuggerClient, hidden) {
var $this = this;
//
// Private stuff
/**
* @type {Object.<object>}
* @private
*/
var _sources = {};
/**
* @type {Array.<string>}
* @private
*/
var _hidden = hidden || [];
/**
* @type {Debugger}
* @private
*/
var _debugger = debuggerClient;
var _isNodeInternal = function (scriptName) {
// node.js internal scripts have no path, just a filename
// regular scripts have always a full path
// (i.e their name contains at least one path separator)
var isFullPath = /[\/\\]/.test(scriptName);
return !isFullPath;
};
var _doAddScript = function (v8data, hidden) {
var inspectorUrl = _debugger.helper.v8NameToInspectorUrl(v8data.name);
/*
"type":"Script",
"mimeType":"text/javascript"
*/
var inspectorScriptData = {
scriptId: String(v8data.id),
url: inspectorUrl,
startLine: v8data.lineOffset,
startColumn: v8data.columnOffset
/* Properties not set:
endLine: undefined,
endColumn: undefined,
isContentScript: undefined,
hasSourceURL: undefined,
*/
};
$this.sourcesMap[inspectorUrl] = inspectorScriptData.scriptId;
if (!_sources[inspectorScriptData.scriptId]) {
_sources[inspectorScriptData.scriptId] = {
hidden: hidden,
v8name: v8data.name,
url: inspectorUrl,
mimeType: "text/javascript",
type: "Script"
};
} else {
if (!_sources[inspectorScriptData.scriptId].v8name) _sources[inspectorScriptData.scriptId].v8name = v8data.name;
if (!_sources[inspectorScriptData.scriptId].url) _sources[inspectorScriptData.scriptId].url = inspectorUrl;
_sources[inspectorScriptData.scriptId].mimeType = "text/javascript";
_sources[inspectorScriptData.scriptId].type = "Script";
}
return inspectorScriptData;
};
var _getSourceMapUrl = function (scriptId, scriptSource, callback) {
var getSource;
if (scriptSource == null) {
console.log('_getSourceMapUrl(%s) - fetching source from V8', scriptId);
getSource = _debugger.getScriptSource.bind(_debugger, scriptId);
} else {
console.log('_getSourceMapUrl(%s) - using the supplied source', scriptId);
getSource = function (cb) {
cb(null, scriptSource);
};
}
getSource(function (err, data) {
_parseSourceMapUrlFromScriptSource(data, callback);
});
};
var _parseSourceMapUrlFromScriptSource = function (source, callback) {
var match = SOURCE_MAP_URL_REGEX.exec(source);
callback(null, match ? match[1] : undefined);
};
//
// Handlers
/**
* After compile handler for the debugger
*
* @param {Object} event
* @private
*/
var _afterCompileHandler = function (event) {
if (!event.script) {
console.demonicLog('[ERROR] Unexpected error: debugger emitted afterCompile event with no script data.');
return;
}
$this.addScript(event.script);
};
//
// Public stuff
/**
* @type {string}
*/
this.mainAppScript = null;
this.sourcesMap = {};
/**
* @param {v8.Debugger} debuggerClient
*/
this.attachDebugger = function (debuggerClient) {
_debugger = debuggerClient;
_debugger.on('afterCompile', _afterCompileHandler);
};
if (debuggerClient) this.attachDebugger(debuggerClient);
/**
* Check if script is hidden
*
* @param {string} scriptPath
* @returns {boolean}
*/
this.isScriptHidden = function (scriptPath) {
return _hidden.some(function fnHiddenScriptMatchesPath(r) {
return r.test(scriptPath);
});
};
/**
* Find script by script ID
*
* @param {number} id
* @returns {object}
*/
this.findScriptByID = function (id) {
return _sources[id];
};
/**
* Get all scripts
*
* @param {Function} callback
*/
this.getAllScripts = function (callback) {
var urls = [];
for (var i in _sources) {
if (!_sources.hasOwnProperty(i)) continue;
urls.push(_sources[i].url);
}
callback(null, urls);
};
var _map = {};
/**
* Set content of a script
*
* @param {string} path
* @param {string} content
*/
this.setContent = function (path, content) {
var id = _map[path];
if (!id || !_sources[id]) return;
if (!_sources[id].source) {
_sources[id].source = content;
}
};
/**
* Add script to the cache
*
* @param {object} v8data
*/
this.addScript = function (v8data) {
var localPath = v8data.name;
var hidden = this.isScriptHidden(localPath) && localPath != this.mainAppScript;
_map[localPath] = v8data.id;
var inspectorScriptData = _doAddScript(v8data, hidden);
console.log('addScript id: %s localPath: %s hidden? %s source? %s', v8data.id, localPath, hidden, !!v8data.source);
if (localPath == 'node.js') _debugger.emit('nodeScriptParsed');
if (hidden || _isNodeInternal(localPath)) {
if (!hidden) _debugger.emit('scriptParsed', inspectorScriptData);
} else {
_getSourceMapUrl(v8data.id, v8data.source, function onGetSourceMapUrlReturn(err, sourceMapUrl) {
if (err) console.demonicLog('[WARNING] Cannot parse SourceMap URL for script %s (id %d). %s', localPath, v8data.id, err);
inspectorScriptData.sourceMapURL = sourceMapUrl;
if (!hidden) _debugger.emit('scriptParsed', inspectorScriptData);
});
}
};
/**
* Reset scripts
*/
this.reset = function (callback) {
_debugger.getScripts(function handleScriptsResponse(err, result) {
if (err) return callback(err);
result.forEach(_debugger.scriptManager.addScript.bind(_debugger.scriptManager));
callback();
});
};
/**
* Destructor
*/
this.destroy = function () {
_debugger = null;
_sources = null;
};
};
module.exports = ScriptManager;