Skip to content
This repository has been archived by the owner on Oct 2, 2021. It is now read-only.

Commit

Permalink
Implement object prop filtering/paging -
Browse files Browse the repository at this point in the history
Handles giant arrays of any size
  • Loading branch information
roblourens committed Sep 15, 2016
1 parent d893f22 commit 65ccf37
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 35 deletions.
4 changes: 2 additions & 2 deletions src/chrome/chromeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ export class ChromeConnection {
return this.sendMessage('Debugger.setVariableValue', <Chrome.Debugger.SetVariableParams>{ callFrameId, scopeNumber, variableName, newValue });
}

public runtime_getProperties(objectId: string, ownProperties: boolean, accessorPropertiesOnly: boolean): Promise<Chrome.Runtime.GetPropertiesResponse> {
return this.sendMessage('Runtime.getProperties', <Chrome.Runtime.GetPropertiesParams>{ objectId, ownProperties, accessorPropertiesOnly, generatePreview: true });
public runtime_getProperties(objectId: string, ownProperties: boolean, accessorPropertiesOnly: boolean, generatePreview?: boolean): Promise<Chrome.Runtime.GetPropertiesResponse> {
return this.sendMessage('Runtime.getProperties', <Chrome.Runtime.GetPropertiesParams>{ objectId, ownProperties, accessorPropertiesOnly, generatePreview });
}

public runtime_evaluate(expression: string, objectGroup = 'dummyObjectGroup', contextId = 1, returnByValue = false): Promise<Chrome.Runtime.EvaluateResponse> {
Expand Down
77 changes: 62 additions & 15 deletions src/chrome/chromeDebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {ChromeConnection} from './chromeConnection';
import * as ChromeUtils from './chromeUtils';
import {formatConsoleMessage} from './consoleHelper';
import * as Chrome from './chromeDebugProtocol';
import {PropertyContainer, ScopeContainer, IVariableContainer} from './variables';
import {PropertyContainer, ScopeContainer, IVariableContainer, isIndexedPropName} from './variables';

import * as errors from '../errors';
import * as utils from '../utils';
Expand Down Expand Up @@ -618,11 +618,15 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
}
}

public getVariablesForObjectId(objectId: string): Promise<DebugProtocol.Variable[]> {
public getVariablesForObjectId(objectId: string, filter?: string, start?: number, count?: number): Promise<DebugProtocol.Variable[]> {
if (typeof start === 'number' && typeof count === 'number') {
return this.getFilteredVariablesForObjectId(objectId, filter, start, count);
}

return Promise.all([
// Need to make two requests to get all properties
this._chromeConnection.runtime_getProperties(objectId, /*ownProperties=*/false, /*accessorPropertiesOnly=*/true),
this._chromeConnection.runtime_getProperties(objectId, /*ownProperties=*/true, /*accessorPropertiesOnly=*/false)
this._chromeConnection.runtime_getProperties(objectId, /*ownProperties=*/false, /*accessorPropertiesOnly=*/true, /*generatePreview=*/true),
this._chromeConnection.runtime_getProperties(objectId, /*ownProperties=*/true, /*accessorPropertiesOnly=*/false, /*generatePreview=*/true)
]).then(getPropsResponses => {
// Sometimes duplicates will be returned - merge all descriptors by name
const propsByName = new Map<string, Chrome.Runtime.PropertyDescriptor>();
Expand All @@ -647,6 +651,12 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {

return Promise.all(variables);
}).then(variables => {
// After retrieving all props, apply the filter
if (filter === 'indexed' || filter === 'named') {
const keepIndexed = filter === 'indexed';
variables = variables.filter(variable => keepIndexed === isIndexedPropName(variable.name));
}

// Sort all variables properly
return variables.sort((var1, var2) => ChromeUtils.compareVariableNames(var1.name, var2.name));
});
Expand All @@ -656,6 +666,36 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
return this.remoteObjectToVariable(propDesc.name, propDesc.value);
}

private getFilteredVariablesForObjectId(objectId: string, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]> {
// No ES6, in case we talk to an old runtime
const getIndexedVariablesFn = `
function getIndexedVariables(start, count) {
var result = [];
for (var i = start; i < (start + count); i++) result[i] = this[i];
return result;
}`;
// TODO order??
const getNamedVariablesFn = `
function getNamedVariablesFn(start, count) {
var result = [];
var ownProps = Object.getOwnProperties(this);
for (var i = start; i < (start + count); i++) result[i] = ownProps[i];
return result;
}`;

const getVarsFn = filter === 'indexed' ? getIndexedVariablesFn : getNamedVariablesFn;
return this._chromeConnection.runtime_callFunctionOn(objectId, getVarsFn, [{ value: start }, { value: count }], /*silent=*/true).then(evalResponse => {
if (evalResponse.error) {
return Promise.reject(errors.errorFromEvaluate(evalResponse.error.message));
} else if (evalResponse.result.exceptionDetails) {
const errMsg = ChromeUtils.errorMessageFromExceptionDetails(evalResponse.result.exceptionDetails);
return Promise.reject(errors.errorFromEvaluate(errMsg));
} else {
return this.getVariablesForObjectId(evalResponse.result.result.objectId, filter);
}
});
}

public source(args: DebugProtocol.SourceArguments): Promise<ISourceResponseBody> {
return this._chromeConnection.debugger_getScriptSource(sourceReferenceToScriptId(args.sourceReference)).then(chromeResponse => {
return {
Expand Down Expand Up @@ -804,16 +844,16 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
public createObjectVariable(name: string, object: Chrome.Runtime.RemoteObject, stringify?: boolean): Promise<DebugProtocol.Variable> {
let propCountP: Promise<IPropCount>;
if (object.subtype === 'array' || object.subtype === 'typedarray') {
if (object.preview.overflow) {
propCountP = this.getArrayNumPropsByEval(object.objectId);
} else {
if (object.preview && !object.preview.overflow) {
propCountP = Promise.resolve(this.getArrayNumPropsByPreview(object));
} else {
propCountP = this.getArrayNumPropsByEval(object.objectId);
}
} else {
if (object.preview.overflow) {
propCountP = this.getObjectNumPropsByEval(object.objectId);
} else {
if (object.preview && !object.preview.overflow) {
propCountP = Promise.resolve(this.getObjectNumPropsByPreview(object));
} else {
propCountP = this.getObjectNumPropsByEval(object.objectId);
}
}

Expand All @@ -829,7 +869,7 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
}

private getArrayNumPropsByEval(objectId: string): Promise<IPropCount> {
const getNumPropsFn = 'function() { return [this.length, Object.keys(this).length - this.length ];}';
const getNumPropsFn = `function() {return [this.length, Object.keys(this).length - this.length ];}`;
return this._chromeConnection.runtime_callFunctionOn(objectId, getNumPropsFn, undefined, /*silent=*/true, /*returnByValue=*/true).then(response => {
if (response.error) {
return Promise.reject(errors.errorFromEvaluate(response.error.message));
Expand All @@ -850,20 +890,27 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
private getArrayNumPropsByPreview(object: Chrome.Runtime.RemoteObject): IPropCount {
let indexedVariables = 0;
let namedVariables = 0;
object.preview.properties.forEach(prop => isNaN(parseInt(prop.name, 10)) ? namedVariables++ : indexedVariables++);
object.preview.properties.forEach(prop => isIndexedPropName(prop.name) ? indexedVariables++ : namedVariables++);
return { indexedVariables, namedVariables };
}

private getObjectNumPropsByEval(objectId: string): Promise<IPropCount> {
const getNumPropsFn = 'function() { return Object.keys(this).length - this.length;}';
return this._chromeConnection.runtime_callFunctionOn(objectId, getNumPropsFn, undefined, /*silent=*/true).then(response => {
// TODO - counting and order?
const getNumPropsFn = `function() {
function isIndexed(name) { return !isNaN(parseInt(name, 10)); }
function isNamed(name) { return isNaN(parseInt(name, 10)); }
var keys = Object.keys(this);
return [keys.filter(isIndexed).length, keys.filter(isNamed).length];
}`;
return this._chromeConnection.runtime_callFunctionOn(objectId, getNumPropsFn, undefined, /*silent=*/true, /*returnByValue=*/true).then(response => {
if (response.error) {
return Promise.reject(errors.errorFromEvaluate(response.error.message));
} else if (response.result.exceptionDetails) {
const errMsg = ChromeUtils.errorMessageFromExceptionDetails(response.result.exceptionDetails);
return Promise.reject(errors.errorFromEvaluate(errMsg));
} else {
return { indexedVariables: 0, namedVariables: response.result.result.value };
return { indexedVariables: response.result.result.value[0], namedVariables: response.result.result.value[1] };
}
});
}
Expand Down
28 changes: 10 additions & 18 deletions src/chrome/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,21 @@

import * as DebugProtocol from 'vscode-debugadapter';

import {Handles} from 'vscode-debugadapter';

import {ChromeDebugAdapter} from './chromeDebugAdapter';
import * as Chrome from './chromeDebugProtocol.d';

import * as utils from '../utils';

export interface IVariableContainer {
objectId: string;
expand(adapter: ChromeDebugAdapter, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]>;
expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise<DebugProtocol.Variable[]>;
setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise<string>;
}

export abstract class BaseVariableContainer implements IVariableContainer {
constructor(public objectId: string) {
}

public expand(adapter: ChromeDebugAdapter, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]> {
return adapter.getVariablesForObjectId(this.objectId).then(variables => {
let filteredVars: DebugProtocol.Variable[] = [];
if (filter === 'indexed' && typeof start === 'number' && typeof count === 'number') {
for (let i = start; i < (count + start) && i < variables.length; i++) filteredVars[i] = variables[i];
} else {
filteredVars = variables;
}

return filteredVars;
});
public expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise<DebugProtocol.Variable[]> {
return adapter.getVariablesForObjectId(this.objectId, filter, start, count);
}

public abstract setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise<string>;
Expand Down Expand Up @@ -59,8 +46,9 @@ export class ScopeContainer extends BaseVariableContainer {
/**
* Call super then insert the 'this' object if needed
*/
public expand(adapter: ChromeDebugAdapter, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]> {
return super.expand(adapter, filter, start, count).then(variables => {
public expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise<DebugProtocol.Variable[]> {
// No filtering in scopes right now
return super.expand(adapter, 'all', start, count).then(variables => {
if (this.thisObj) {
// If this is a scope that should have the 'this', prop, insert it at the top of the list
return adapter.propertyDescriptorToVariable(<any>{ name: 'this', value: this.thisObj }).then(thisObjVar => {
Expand All @@ -77,3 +65,7 @@ export class ScopeContainer extends BaseVariableContainer {
return adapter._setVariableValue(this._frameId, this._scopeIndex, name, value);
}
}

export function isIndexedPropName(name: string): boolean {
return !isNaN(parseInt(name, 10));
}

0 comments on commit 65ccf37

Please sign in to comment.