Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/boss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"dependencies": {
"@ulixee/apps-chromealive-core": "1.5.4",
"@ulixee/default-browser-emulator": "1.5.4",
"@ulixee/commons": "1.5.8",
"@ulixee/commons": "1.5.9",
"@ulixee/server": "1.5.4",
"electron-positioner": "^4.1.0",
"tar": "^6.1.11",
Expand Down
2 changes: 1 addition & 1 deletion apps/chromealive-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"license": "MIT",
"dependencies": {
"@ulixee/hero-interfaces": "1.5.4",
"@ulixee/commons": "1.5.8",
"@ulixee/commons": "1.5.9",
"@ulixee/hero-plugin-utils": "1.5.4",
"@ulixee/hero-puppet-chrome": "1.5.4",
"@ulixee/default-browser-emulator": "1.5.4",
Expand Down
2 changes: 1 addition & 1 deletion apps/chromealive-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@headlessui/vue": "^1.4.1",
"@heroicons/vue": "^1.0.4",
"@tailwindcss/forms": "^0.3.3",
"@ulixee/commons": "1.5.8",
"@ulixee/commons": "1.5.9",
"@ulixee/apps-chromealive-core": "1.5.4",
"@webcomponents/custom-elements": "^1.5.0",
"core-js": "^3.6.5",
Expand Down
2 changes: 1 addition & 1 deletion apps/chromealive-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"vue-json-pretty": "^1.7.1",
"json5": "^2.2.0",
"@ulixee/apps-chromealive-interfaces": "1.5.4",
"@ulixee/commons": "1.5.8"
"@ulixee/commons": "1.5.9"
},
"devDependencies": {
"@tailwindcss/postcss7-compat": "^2.2.7",
Expand Down
1 change: 0 additions & 1 deletion apps/chromealive-ui/src/pages/pagestate-panel/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,6 @@ export default Vue.defineComponent({
if (this.editingStateName || this.draggingSessionId) return;
this.focusedStateName = state.state;

console.log('focus on state', state.state, state.heroSessionIds, this.focusedSessionId);
if (!state.heroSessionIds.includes(this.focusedSessionId)) {
const firstSession = this.data.heroSessions.find(x => x.id === state.heroSessionIds[0]);
if (firstSession) {
Expand Down
2 changes: 1 addition & 1 deletion apps/chromealive/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"tar": "^6.1.11",
"compare-versions": "^3.6.0",
"electron-log": "^4.4.1",
"@ulixee/commons": "1.5.8",
"@ulixee/commons": "1.5.9",
"@ulixee/apps-chromealive-interfaces": "^1.5.4",
"electron-context-menu": "^3.1.1",
"ws": "^7.4.6"
Expand Down
5 changes: 5 additions & 0 deletions commons/interfaces/ISourceCodeLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default interface ISourceCodeLocation {
filename: string;
line: number;
column: number;
}
27 changes: 27 additions & 0 deletions commons/lib/SourceLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import ISourceCodeLocation from '../interfaces/ISourceCodeLocation';
import { SourceMapSupport } from './SourceMapSupport';

export default class SourceLoader {
private static sourceLines: { [source: string]: string[] } = {};

static getSource(codeLocation: ISourceCodeLocation): ISourceCodeLocation & { code: string } {
const originalSourcePosition = SourceMapSupport.getOriginalSourcePosition(codeLocation);
if (originalSourcePosition.source !== codeLocation.filename) {
codeLocation = {
line: originalSourcePosition.line,
filename: originalSourcePosition.source,
column: originalSourcePosition.column,
};
}
if (!this.sourceLines[codeLocation.filename]) {
const file = SourceMapSupport.getFileContents(codeLocation.filename);
this.sourceLines[codeLocation.filename] = file.split(/\r?\n/);
}

const code = this.sourceLines[codeLocation.filename][codeLocation.line - 1];
return {
code,
...codeLocation,
};
}
}
267 changes: 263 additions & 4 deletions commons/lib/SourceMapSupport.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,265 @@
import { install } from 'source-map-support';
import { MappedPosition, SourceMapConsumer } from 'source-map-js';
import * as fs from 'fs';
import * as path from 'path';
import ISourceCodeLocation from '../interfaces/ISourceCodeLocation';
import { URL } from 'url';

if (!Error[Symbol.for('source-map-support')]) {
Error[Symbol.for('source-map-support')] = true;
install({ handleUncaughtExceptions: false });
// ATTRIBUTION: forked from https://github.com/evanw/node-source-map-support

const sourceMapDataUrlRegex = /^data:application\/json[^,]+base64,/;
const sourceMapUrlRegex =
/(?:\/\/[@#][\s]*sourceMappingURL=([^\s'"]+)[\s]*$)|(?:\/\*[@#][\s]*sourceMappingURL=([^\s*'"]+)[\s]*(?:\*\/)[\s]*$)/gm;
const fileUrlPrefix = 'file://';

export class SourceMapSupport {
private static sourceMapCache: { [source: string]: { map: SourceMapConsumer; url: string } } = {};
private static resolvedPathCache: { [file_url: string]: string } = {};
private static fileContentsCache: { [filepath: string]: string } = {};

static resetCache(): void {
this.sourceMapCache = {};
this.resolvedPathCache = {};
this.fileContentsCache = {};
}

static install(): void {
if (!Error[Symbol.for('source-map-support')]) {
Error[Symbol.for('source-map-support')] = true;
Error.prepareStackTrace = this.prepareStackTrace.bind(this);
}
}

static getOriginalSourcePosition(position: ISourceCodeLocation): MappedPosition {
this.sourceMapCache[position.filename] ??= this.retrieveSourceMap(position.filename);

const sourceMap = this.sourceMapCache[position.filename];
if (sourceMap?.map) {
const originalPosition = sourceMap.map.originalPositionFor(position);

// Only return the original position if a matching line was found
if (originalPosition.source) {
originalPosition.source = this.resolvePath(sourceMap.url, originalPosition.source);
return originalPosition;
}
}

return {
source: position.filename,
line: position.line,
column: position.column,
};
}

static getFileContents(filepath: string, cache = true): string {
if (this.fileContentsCache[filepath]) return this.fileContentsCache[filepath];

const originalFilepath = filepath;
// Trim the path to make sure there is no extra whitespace.
let lookupFilepath: string | URL = filepath.trim();
if (filepath.startsWith(fileUrlPrefix)) {
lookupFilepath = new URL(filepath);
}

let data: string = null;
try {
data = fs.readFileSync(lookupFilepath, 'utf8');
} catch (err) {
// couldn't read
}
if (cache) {
this.fileContentsCache[filepath] = data;
this.fileContentsCache[originalFilepath] = data;
}
return data;
}

private static prepareStackTrace(error: Error, stack: NodeJS.CallSite[]): string {
const name = error.name ?? error[Symbol.toStringTag] ?? error.constructor?.name ?? 'Error';
const message = error.message ?? '';
const errorString = name + ': ' + message;

// track fn name as we go backwards through stack
const processedStack = [];
let containingFnName: string = null;
for (let i = stack.length - 1; i >= 0; i--) {
let frame = stack[i];
if (frame.isNative()) {
containingFnName = null;
} else {
const filename = frame.getFileName() || (frame as any).getScriptNameOrSourceURL();
if (filename) {
const position = this.getOriginalSourcePosition({
filename,
line: frame.getLineNumber(),
column: frame.getColumnNumber() - 1,
});
if (position.source !== filename) {
const fnName = containingFnName ?? frame.getFunctionName();
containingFnName = position.name;
frame = new Proxy(frame, {
get(target: NodeJS.CallSite, p: string | symbol): any {
if (p === 'getFunctionName') return () => fnName;
if (p === 'getFileName') return () => position.source;
if (p === 'getScriptNameOrSourceURL') return () => position.source;
if (p === 'getLineNumber') return () => position.line;
if (p === 'getColumnNumber') return () => position.column + 1;
if (p === 'toString') return CallSiteToString.bind(frame);

return target[p]?.bind(target);
},
});
}
}
}

processedStack.unshift(`\n at ${frame.toString()}`);
}
return errorString + processedStack.join('');
}

private static retrieveSourceMap(source: string): { url: string; map: SourceMapConsumer } {
const fileData = this.getFileContents(source, false);

// Find the *last* sourceMappingURL to avoid picking up sourceMappingURLs from comments, strings, etc.
let sourceMappingURL: string;
let sourceMapData: string;

let match: RegExpMatchArray;
while ((match = sourceMapUrlRegex.exec(fileData))) {
sourceMappingURL = match[1];
}

if (sourceMappingURL) {
if (sourceMapDataUrlRegex.test(sourceMappingURL)) {
const rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1);
sourceMapData = Buffer.from(rawData, 'base64').toString();
sourceMappingURL = source;
} else {
sourceMappingURL = this.resolvePath(source, sourceMappingURL);
sourceMapData = this.getFileContents(sourceMappingURL);
}
}

if (!sourceMapData) {
return {
url: null,
map: null,
};
}

const rawData = JSON.parse(sourceMapData);
return {
url: sourceMappingURL,
map: new SourceMapConsumer(rawData),
};
}

private static resolvePath(base: string, relative: string): string {
if (!base) return relative;
const key = `${base}__${relative}`;

if (!this.resolvedPathCache[key]) {
let protocol = base.startsWith(fileUrlPrefix) ? fileUrlPrefix : '';

let basePath = path.dirname(base).slice(protocol.length);

// handle file:///C:/ paths
if (protocol && /^\/\w:/.test(basePath)) {
protocol += '/';
basePath = basePath.slice(1);
}

this.resolvedPathCache[key] = protocol + path.resolve(basePath, relative);
}
return this.resolvedPathCache[key];
}
}

SourceMapSupport.install();

// Converted from the V8 source code at:
// https://github.com/v8/v8/blob/dc712da548c7fb433caed56af9a021d964952728/src/objects/stack-frame-info.cc#L344-L393
function CallSiteToString(
this: NodeJS.CallSite & {
getScriptNameOrSourceURL(): string;
isAsync(): boolean;
isPromiseAll?(): boolean;
isPromiseAny?(): boolean;
getPromiseIndex?(): number;
},
) {
let fileName;
let fileLocation = '';
if (this.isNative()) {
fileLocation = 'native';
} else {
fileName = this.getScriptNameOrSourceURL();
if (!fileName && this.isEval()) {
fileLocation = this.getEvalOrigin();
fileLocation += ', '; // Expecting source position to follow.
}

if (fileName) {
fileLocation += fileName;
} else {
// Source code does not originate from a file and is not native, but we
// can still get the source position inside the source string, e.g. in
// an eval string.
fileLocation += '<anonymous>';
}
const lineNumber = this.getLineNumber();
if (lineNumber != null) {
fileLocation += ':' + lineNumber;
const columnNumber = this.getColumnNumber();
if (columnNumber) {
fileLocation += ':' + columnNumber;
}
}
}

let line = '';
const isAsync = this.isAsync ? this.isAsync() : false;
if (isAsync) {
line += 'async ';
const isPromiseAll = this.isPromiseAll ? this.isPromiseAll() : false;
const isPromiseAny = this.isPromiseAny ? this.isPromiseAny() : false;
if (isPromiseAny || isPromiseAll) {
line += isPromiseAll ? 'Promise.all (index ' : 'Promise.any (index ';
const promiseIndex = this.getPromiseIndex();
line += promiseIndex + ')';
}
}
const functionName = this.getFunctionName();
let addSuffix = true;
const isConstructor = this.isConstructor();
const isMethodCall = !(this.isToplevel() || isConstructor);
if (isMethodCall) {
const typeName = this.getTypeName();
const methodName = this.getMethodName();
if (functionName) {
if (typeName && functionName.indexOf(typeName) != 0) {
line += typeName + '.';
}
line += functionName;
if (
methodName &&
functionName.indexOf('.' + methodName) != functionName.length - methodName.length - 1
) {
line += ' [as ' + methodName + ']';
}
} else {
line += typeName + '.' + (methodName || '<anonymous>');
}
} else if (isConstructor) {
line += 'new ' + (functionName || '<anonymous>');
} else if (functionName) {
line += functionName;
} else {
line += fileLocation;
addSuffix = false;
}
if (addSuffix) {
line += ' (' + fileLocation + ')';
}
return line;
}
Loading