Skip to content

Commit 2da78dc

Browse files
committed
Fixed #20
1 parent 3321b83 commit 2da78dc

File tree

3 files changed

+82
-23
lines changed

3 files changed

+82
-23
lines changed

src/tests/adapter.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,5 +410,30 @@ describe('The adapter', () => {
410410
});
411411
});
412412
});
413+
414+
// iOS specifc tests
415+
if (platform == 'ios') {
416+
it(`${meta} should not hang on evaluating watch expression on call frame with unknown source`, () => {
417+
let appRoot = context.getAppPath('JsApp');
418+
419+
let scenario = new Scenario(dc);
420+
scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator);
421+
422+
423+
return Promise.all<any>([
424+
scenario.start(),
425+
scenario.client.onNextTime('stopped').then(e => {
426+
return scenario.client.stackTraceRequest({ threadId: e.body.threadId }).then(response => {
427+
let callFrame = response.body.stackFrames.filter(callFrame => !callFrame.source.path && !callFrame.source.sourceReference)[0];
428+
return scenario.client.evaluateRequest({ expression: 'Math.random()', frameId: callFrame.id, context: 'watch' }).then(response => {
429+
assert.fail(undefined, undefined, 'Evaluate request should fail');
430+
}, response => {
431+
assert.equal(response.message, '-', 'error message mismatch');
432+
});
433+
});
434+
})
435+
]);
436+
});
437+
}
413438
});
414439
});

src/webkit/webKitDebugAdapter.ts

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
299299
private onScriptParsed(script: WebKitProtocol.Debugger.Script): void {
300300
this._scriptsById.set(script.scriptId, script);
301301

302-
if (!this.isExtensionScript(script)) {
302+
if (this.scriptIsNotAnonymous(script)) {
303303
this.fireEvent({ seq: 0, type: 'event', event: 'scriptParsed', body: { scriptUrl: script.url, sourceMapURL: script.sourceMapURL }});
304304
}
305305
}
@@ -491,28 +491,50 @@ export class WebKitDebugAdapter implements IDebugAdapter {
491491

492492
const stackFrames: DebugProtocol.StackFrame[] = stack
493493
.map((callFrame: WebKitProtocol.Debugger.CallFrame, i: number) => {
494-
const script = this._scriptsById.get(callFrame.location.scriptId);
495-
const line = callFrame.location.lineNumber;
496-
const column = callFrame.location.columnNumber;
494+
const sourceReference = scriptIdToSourceReference(callFrame.location.scriptId);
495+
const scriptId = callFrame.location.scriptId;
496+
const script = this._scriptsById.get(scriptId);
497497

498498
let source: DebugProtocol.Source;
499-
if (script) {
500-
// When the script has a url and isn't a content script, send the name and path fields. PathTransformer will
501-
// attempt to resolve it to a script in the workspace. Otherwise, send the name and sourceReference fields.
502-
source = (script.url && !this.isExtensionScript(script)) ?
503-
{
499+
if (this.scriptIsNotUnknown(scriptId)) {
500+
// We have received Debugger.scriptParsed event for the script.
501+
if (this.scriptIsNotAnonymous(script)) {
502+
/**
503+
* We have received non-empty url with the Debugger.scriptParsed event.
504+
* We set the url value to the path property. Later on, the PathTransformer will attempt to resolve it to a script in the app root folder.
505+
* In case it fails to resolve it, we also set the sourceReference field in order to allow the client to send source request to retrieve the source.
506+
* If the PathTransformer resolves the url successfully, it will change the value of sourceReference to 0.
507+
*/
508+
source = {
504509
name: path.basename(script.url),
505510
path: script.url,
506511
sourceReference: scriptIdToSourceReference(script.scriptId) // will be 0'd out by PathTransformer if not needed
507-
} :
508-
{
509-
// Name should be undefined, work around VS Code bug 20274
510-
name: undefined,
511-
sourceReference: scriptIdToSourceReference(script.scriptId)
512512
};
513+
}
514+
else {
515+
/**
516+
* We have received Debugger.scriptParsed event with empty url value.
517+
* Sending only the sourceId will make the client to send source request to retrieve the source of the script.
518+
*/
519+
source = {
520+
name: 'anonymous source',
521+
sourceReference: sourceReference
522+
};
523+
}
513524
}
514525
else {
515-
source = { name: 'eval: Unknown' };
526+
/**
527+
* Unknown script. No Debugger.scriptParsed event received for the script.
528+
*
529+
* Some 'internal scripts' are intentionally referenced by id equal to 0. Others have id > 0 but no Debugger.scriptParsed event is sent when parsed.
530+
* In both cases we can't get its source code. If we send back a zero sourceReference the VS Code client will not send source request.
531+
* The most we can do is to include a dummy stack frame with no source associated and without specifing the sourceReference.
532+
*/
533+
source = {
534+
name: 'unknown source',
535+
origin: 'internal module',
536+
sourceReference: 0
537+
};
516538
}
517539

518540
// If the frame doesn't have a function name, it's either an anonymous function
@@ -521,9 +543,9 @@ export class WebKitDebugAdapter implements IDebugAdapter {
521543
return {
522544
id: i,
523545
name: frameName,
524-
source,
525-
line: line,
526-
column
546+
source: source,
547+
line: callFrame.location.lineNumber,
548+
column: callFrame.location.columnNumber
527549
};
528550
});
529551

@@ -588,6 +610,9 @@ export class WebKitDebugAdapter implements IDebugAdapter {
588610

589611
public source(args: DebugProtocol.SourceArguments): Promise<ISourceResponseBody> {
590612
return this._webKitConnection.debugger_getScriptSource(sourceReferenceToScriptId(args.sourceReference)).then(webkitResponse => {
613+
if (webkitResponse.error) {
614+
throw new Error(webkitResponse.error.message);
615+
}
591616
return { content: webkitResponse.result.scriptSource };
592617
});
593618
}
@@ -606,8 +631,12 @@ export class WebKitDebugAdapter implements IDebugAdapter {
606631
public evaluate(args: DebugProtocol.EvaluateArguments): Promise<IEvaluateResponseBody> {
607632
let evalPromise: Promise<any>;
608633
if (this.paused) {
609-
const callFrameId = this._currentStack[args.frameId].callFrameId;
610-
evalPromise = this._webKitConnection.debugger_evaluateOnCallFrame(callFrameId, args.expression);
634+
const callFrame = this._currentStack[args.frameId];
635+
if (!this.scriptIsNotUnknown(callFrame.location.scriptId)) {
636+
// The iOS debugger backend hangs and stops responding after receiving evaluate request on call frame which has unknown source.
637+
throw new Error('-'); // The message will be printed in the VS Code UI
638+
}
639+
evalPromise = this._webKitConnection.debugger_evaluateOnCallFrame(callFrame.callFrameId, args.expression);
611640
} else {
612641
evalPromise = this._webKitConnection.runtime_evaluate(args.expression);
613642
}
@@ -648,8 +677,14 @@ export class WebKitDebugAdapter implements IDebugAdapter {
648677
return result;
649678
}
650679

651-
private isExtensionScript(script: WebKitProtocol.Debugger.Script): boolean {
652-
return script.isContentScript || !script.url || script.url.startsWith('extensions::') || script.url.startsWith('chrome-extension://');
680+
// Returns true if the script has url supplied in Debugger.scriptParsed event
681+
private scriptIsNotAnonymous(script: WebKitProtocol.Debugger.Script): boolean {
682+
return script && !!script.url;
683+
}
684+
685+
// Returns true if Debugger.scriptParsed event is received for the provided script id
686+
private scriptIsNotUnknown(scriptId: WebKitProtocol.Debugger.ScriptId): boolean {
687+
return !!this._scriptsById.get(scriptId);
653688
}
654689
}
655690

src/webkit/webKitProtocol.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ declare namespace WebKitProtocol {
2828
startColumn?: number;
2929
endLine?: number;
3030
endColumn?: number;
31-
isInternalScript?: boolean;
3231
sourceMapURL?: string;
3332
isContentScript?: boolean;
3433
}

0 commit comments

Comments
 (0)