From fa9547d0c62805a8b40d4f4e44d0082e2f3dc1d3 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 30 Aug 2012 14:10:07 -0700 Subject: [PATCH] Bug 694539: Implement 'long string' grips. r=jimb --- toolkit/devtools/debugger/dbg-client.jsm | 69 ++++- .../debugger/server/dbg-script-actors.js | 254 ++++++++++++++---- .../devtools/debugger/server/dbg-server.js | 3 + .../tests/unit/test_longstringactor.js | 105 ++++++++ .../tests/unit/test_longstringgrips-01.js | 71 +++++ .../tests/unit/test_longstringgrips-02.js | 60 +++++ .../devtools/debugger/tests/unit/xpcshell.ini | 3 + 7 files changed, 497 insertions(+), 68 deletions(-) create mode 100644 toolkit/devtools/debugger/tests/unit/test_longstringactor.js create mode 100644 toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js create mode 100644 toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js diff --git a/toolkit/devtools/debugger/dbg-client.jsm b/toolkit/devtools/debugger/dbg-client.jsm index 26114f924049..01d0ed2210ce 100644 --- a/toolkit/devtools/debugger/dbg-client.jsm +++ b/toolkit/devtools/debugger/dbg-client.jsm @@ -208,7 +208,8 @@ const DebugProtocolTypes = { "prototypeAndProperties": "prototypeAndProperties", "resume": "resume", "scripts": "scripts", - "setBreakpoint": "setBreakpoint" + "setBreakpoint": "setBreakpoint", + "substring": "substring" }; const ROOT_ACTOR_NAME = "root"; @@ -514,6 +515,7 @@ function ThreadClient(aClient, aActor) { this._actor = aActor; this._frameCache = []; this._scriptCache = {}; + this._pauseGrips = {}; } ThreadClient.prototype = { @@ -878,10 +880,6 @@ ThreadClient.prototype = { * A pause-lifetime object grip returned by the protocol. */ pauseGrip: function TC_pauseGrip(aGrip) { - if (!this._pauseGrips) { - this._pauseGrips = {}; - } - if (aGrip.actor in this._pauseGrips) { return this._pauseGrips[aGrip.actor]; } @@ -891,6 +889,22 @@ ThreadClient.prototype = { return client; }, + /** + * Return an instance of LongStringClient for the given long string grip. + * + * @param aGrip Object + * The long string grip returned by the protocol. + */ + longString: function TC_longString(aGrip) { + if (aGrip.actor in this._pauseGrips) { + return this._pauseGrips[aGrip.actor]; + } + + let client = new LongStringClient(this._client, aGrip); + this._pauseGrips[aGrip.actor] = client; + return client; + }, + /** * Invalidate pause-lifetime grip clients and clear the list of * current grip clients. @@ -899,7 +913,7 @@ ThreadClient.prototype = { for each (let grip in this._pauseGrips) { grip.valid = false; } - this._pauseGrips = null; + this._pauseGrips = {}; }, /** @@ -933,9 +947,7 @@ function GripClient(aClient, aGrip) GripClient.prototype = { get actor() { return this._grip.actor }, - _valid: true, - get valid() { return this._valid; }, - set valid(aValid) { this._valid = !!aValid; }, + valid: true, /** * Request the name of the function and its formal parameters. @@ -1017,6 +1029,45 @@ GripClient.prototype = { } }; +/** + * A LongStringClient provides a way to access "very long" strings from the + * debugger server. + * + * @param aClient DebuggerClient + * The debugger client parent. + * @param aGrip Object + * A pause-lifetime long string grip returned by the protocol. + */ +function LongStringClient(aClient, aGrip) { + this._grip = aGrip; + this._client = aClient; +} + +LongStringClient.prototype = { + get actor() { return this._grip.actor; }, + get length() { return this._grip.length; }, + + valid: true, + + /** + * Get the substring of this LongString from aStart to aEnd. + * + * @param aStart Number + * The starting index. + * @param aEnd Number + * The ending index. + * @param aCallback Function + * The function called when we receive the substring. + */ + substring: function LSC_substring(aStart, aEnd, aCallback) { + let packet = { to: this.actor, + type: DebugProtocolTypes.substring, + start: aStart, + end: aEnd }; + this._client.request(packet, aCallback); + } +}; + /** * Breakpoint clients are used to remove breakpoints that are no longer used. * diff --git a/toolkit/devtools/debugger/server/dbg-script-actors.js b/toolkit/devtools/debugger/server/dbg-script-actors.js index 3e051467662b..97d60cca6f24 100644 --- a/toolkit/devtools/debugger/server/dbg-script-actors.js +++ b/toolkit/devtools/debugger/server/dbg-script-actors.js @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; + /** * JSD2 actors. */ @@ -786,6 +787,11 @@ ThreadActor.prototype = { */ createValueGrip: function TA_createValueGrip(aValue) { let type = typeof(aValue); + + if (type === "string" && this._stringIsLong(aValue)) { + return this.longStringGrip(aValue); + } + if (type === "boolean" || type === "string" || type === "number") { return aValue; } @@ -872,6 +878,42 @@ ThreadActor.prototype = { return this.objectGrip(aValue, this.threadLifetimePool); }, + /** + * Create a grip for the given string. + * + * @param aString String + * The string we are creating a grip for. + */ + longStringGrip: function TA_longStringGrip(aString) { + if (!this._pausePool) { + throw new Error("LongString grip requested while not paused."); + } + + if (!this._pausePool.longStringActors) { + this._pausePool.longStringActors = {}; + } + + if (this._pausePool.longStringActors.hasOwnProperty(aString)) { + return this._pausePool.longStringActors[aString].grip(); + } + + let actor = new LongStringActor(aString, this); + this._pausePool.addActor(actor); + this._pausePool.longStringActors[aString] = actor; + return actor.grip(); + }, + + /** + * Returns true if the string is long enough to use a LongStringActor instead + * of passing the value directly over the protocol. + * + * @param aString String + * The string we are checking the length of. + */ + _stringIsLong: function TA__stringIsLong(aString) { + return aString.length >= DebuggerServer.LONG_STRING_LENGTH; + }, + // JS Debugger API hooks. /** @@ -1020,6 +1062,75 @@ PauseActor.prototype = { }; +/** + * A base actor for any actors that should only respond receive messages in the + * paused state. Subclasses may expose a `threadActor` which is used to help + * determine when we are in a paused state. Subclasses should set their own + * "constructor" property if they want better error messages. You should never + * instantiate a PauseScopedActor directly, only through subclasses. + */ +function PauseScopedActor() +{ +} + +/** + * A function decorator for creating methods to handle protocol messages that + * should only be received while in the paused state. + * + * @param aMethod Function + * The function we are decorating. + */ +PauseScopedActor.withPaused = function PSA_withPaused(aMethod) { + return function () { + if (this.isPaused()) { + return aMethod.apply(this, arguments); + } else { + return this._wrongState(); + } + }; +}; + +PauseScopedActor.prototype = { + + /** + * Returns true if we are in the paused state. + */ + isPaused: function PSA_isPaused() { + // When there is not a ThreadActor available (like in the webconsole) we + // have to be optimistic and assume that we are paused so that we can + // respond to requests. + return this.threadActor ? this.threadActor.state === "paused" : true; + }, + + /** + * Returns the wrongState response packet for this actor. + */ + _wrongState: function PSA_wrongState() { + return { + error: "wrongState", + message: this.constructor.name + + " actors can only be accessed while the thread is paused." + } + } +}; + + +/** + * Utility function for updating an object with the properties of another + * object. + * + * @param aTarget Object + * The object being updated. + * @param aNewAttrs Object + * The new attributes being set on the target. + */ +function update(aTarget, aNewAttrs) { + for (let key in aNewAttrs) { + aTarget[key] = aNewAttrs[key]; + } +} + + /** * Creates an actor for the specified object. * @@ -1034,13 +1145,11 @@ function ObjectActor(aObj, aThreadActor) this.threadActor = aThreadActor; } -ObjectActor.prototype = { - actorPrefix: "obj", +ObjectActor.prototype = Object.create(PauseScopedActor.prototype); - WRONG_STATE_RESPONSE: { - error: "wrongState", - message: "Object actors can only be accessed while the thread is paused." - }, +update(ObjectActor.prototype, { + constructor: ObjectActor, + actorPrefix: "obj", /** * Returns a grip for this actor for returning in a protocol message. @@ -1066,14 +1175,11 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onOwnPropertyNames: function OA_onOwnPropertyNames(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } - + onOwnPropertyNames: + PauseScopedActor.withPaused(function OA_onOwnPropertyNames(aRequest) { return { from: this.actorID, ownPropertyNames: this.obj.getOwnPropertyNames() }; - }, + }), /** * Handle a protocol request to provide the prototype and own properties of @@ -1082,11 +1188,8 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onPrototypeAndProperties: function OA_onPrototypeAndProperties(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } - + onPrototypeAndProperties: + PauseScopedActor.withPaused(function OA_onPrototypeAndProperties(aRequest) { let ownProperties = {}; for each (let name in this.obj.getOwnPropertyNames()) { try { @@ -1102,7 +1205,7 @@ ObjectActor.prototype = { return { from: this.actorID, prototype: this.threadActor.createValueGrip(this.obj.proto), ownProperties: ownProperties }; - }, + }), /** * Handle a protocol request to provide the prototype of the object. @@ -1110,14 +1213,10 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onPrototype: function OA_onPrototype(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } - + onPrototype: PauseScopedActor.withPaused(function OA_onPrototype(aRequest) { return { from: this.actorID, prototype: this.threadActor.createValueGrip(this.obj.proto) }; - }, + }), /** * Handle a protocol request to provide the property descriptor of the @@ -1126,10 +1225,7 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onProperty: function OA_onProperty(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } + onProperty: PauseScopedActor.withPaused(function OA_onProperty(aRequest) { if (!aRequest.name) { return { error: "missingParameter", message: "no property name was specified" }; @@ -1138,7 +1234,7 @@ ObjectActor.prototype = { let desc = this.obj.getOwnPropertyDescriptor(aRequest.name); return { from: this.actorID, descriptor: this._propertyDescriptor(desc) }; - }, + }), /** * A helper method that creates a property descriptor for the provided object, @@ -1167,11 +1263,7 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onDecompile: function OA_onDecompile(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } - + onDecompile: PauseScopedActor.withPaused(function OA_onDecompile(aRequest) { if (this.obj.class !== "Function") { return { error: "objectNotFunction", message: "decompile request is only valid for object grips " + @@ -1180,7 +1272,7 @@ ObjectActor.prototype = { return { from: this.actorID, decompiledCode: this.obj.decompile(!!aRequest.pretty) }; - }, + }), /** * Handle a protocol request to provide the lexical scope of a function. @@ -1188,11 +1280,7 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onScope: function OA_onScope(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } - + onScope: PauseScopedActor.withPaused(function OA_onScope(aRequest) { if (this.obj.class !== "Function") { return { error: "objectNotFunction", message: "scope request is only valid for object grips with a" + @@ -1212,7 +1300,7 @@ ObjectActor.prototype = { // use the 'scope' request in the debugger frontend. return { name: this.obj.name || null, scope: envActor.form(this.obj) }; - }, + }), /** * Handle a protocol request to provide the name and parameters of a function. @@ -1220,11 +1308,7 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onNameAndParameters: function OA_onNameAndParameters(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } - + onNameAndParameters: PauseScopedActor.withPaused(function OA_onNameAndParameters(aRequest) { if (this.obj.class !== "Function") { return { error: "objectNotFunction", message: "nameAndParameters request is only valid for object " + @@ -1233,7 +1317,7 @@ ObjectActor.prototype = { return { name: this.obj.name || null, parameters: this.obj.parameterNames }; - }, + }), /** * Handle a protocol request to promote a pause-lifetime grip to a @@ -1242,13 +1326,9 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onThreadGrip: function OA_onThreadGrip(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } - + onThreadGrip: PauseScopedActor.withPaused(function OA_onThreadGrip(aRequest) { return { threadGrip: this.threadActor.threadObjectGrip(this.obj) }; - }, + }), /** * Handle a protocol request to release a thread-lifetime grip. @@ -1256,10 +1336,7 @@ ObjectActor.prototype = { * @param aRequest object * The protocol request object. */ - onRelease: function OA_onRelease(aRequest) { - if (this.threadActor.state !== "paused") { - return this.WRONG_STATE_RESPONSE; - } + onRelease: PauseScopedActor.withPaused(function OA_onRelease(aRequest) { if (this.registeredPool !== this.threadActor.threadLifetimePool) { return { error: "notReleasable", message: "only thread-lifetime actors can be released." }; @@ -1267,8 +1344,8 @@ ObjectActor.prototype = { this.release(); return {}; - }, -}; + }), +}); ObjectActor.prototype.requestTypes = { "nameAndParameters": ObjectActor.prototype.onNameAndParameters, @@ -1283,6 +1360,65 @@ ObjectActor.prototype.requestTypes = { }; +/** + * Creates an actor for the specied "very long" string. "Very long" is specified + * at the server's discretion. + * + * @param aString String + * The string. + */ +function LongStringActor(aString) +{ + this.string = aString; + this.stringLength = aString.length; +} + +LongStringActor.prototype = { + + actorPrefix: "longString", + + disconnect: function LSA_disconnect() { + // Because longStringActors is not a weak map, we won't automatically leave + // it so we need to manually leave on disconnect so that we don't leak + // memory. + if (this.registeredPool && this.registeredPool.longStringActors) { + delete this.registeredPool.longStringActors[this.actorID]; + } + }, + + /** + * Returns a grip for this actor for returning in a protocol message. + */ + grip: function LSA_grip() { + return { + "type": "longString", + "initial": this.string.substring( + 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH), + "length": this.stringLength, + "actor": this.actorID + }; + }, + + /** + * Handle a request to extract part of this actor's string. + * + * @param aRequest object + * The protocol request object. + */ + onSubstring: function LSA_onSubString(aRequest) { + return { + "from": this.actorID, + "substring": this.string.substring(aRequest.start, aRequest.end) + }; + } + +}; + +LongStringActor.prototype.requestTypes = { + "substring": LongStringActor.prototype.onSubstring +}; + + /** * Creates an actor for the specified stack frame. * diff --git a/toolkit/devtools/debugger/server/dbg-server.js b/toolkit/devtools/debugger/server/dbg-server.js index 2cfab0ef7ce8..a5fe837c9eae 100644 --- a/toolkit/devtools/debugger/server/dbg-server.js +++ b/toolkit/devtools/debugger/server/dbg-server.js @@ -61,6 +61,9 @@ var DebuggerServer = { xpcInspector: null, _allowConnection: null, + LONG_STRING_LENGTH: 10000, + LONG_STRING_INITIAL_LENGTH: 1000, + /** * Initialize the debugger server. * diff --git a/toolkit/devtools/debugger/tests/unit/test_longstringactor.js b/toolkit/devtools/debugger/tests/unit/test_longstringactor.js new file mode 100644 index 000000000000..f2b50ddb2e3f --- /dev/null +++ b/toolkit/devtools/debugger/tests/unit/test_longstringactor.js @@ -0,0 +1,105 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() +{ + let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Components.interfaces.mozIJSSubScriptLoader); + loader.loadSubScript("chrome://global/content/devtools/dbg-script-actors.js"); + + test_LSA_disconnect(); + test_LSA_grip(); + test_LSA_onSubstring(); +} + +const TEST_STRING = "This is a very long string!"; + +function makeMockLongStringActor() +{ + let string = TEST_STRING; + let actor = new LongStringActor(string); + actor.actorID = "longString1"; + actor.registeredPool = { + longStringActors: { + longString1: actor + } + }; + return actor; +} + +function test_LSA_disconnect() +{ + let actor = makeMockLongStringActor(); + do_check_eq(actor.registeredPool.longStringActors[actor.actorID], actor); + + actor.disconnect(); + do_check_eq(actor.registeredPool.longStringActors[actor.actorID], void 0); +} + +function test_LSA_substring() +{ + let actor = makeMockLongStringActor(); + do_check_eq(actor._substring(0, 4), TEST_STRING.substring(0, 4)); + do_check_eq(actor._substring(6, 9), TEST_STRING.substring(6, 9)); + do_check_eq(actor._substring(0, TEST_STRING.length), TEST_STRING); +} + +function test_LSA_grip() +{ + let actor = makeMockLongStringActor(); + + let grip = actor.grip(); + do_check_eq(grip.type, "longString"); + do_check_eq(grip.initial, TEST_STRING.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)); + do_check_eq(grip.length, TEST_STRING.length); + do_check_eq(grip.actor, actor.actorID); +} + +function test_LSA_onSubstring() +{ + let actor = makeMockLongStringActor(); + let response; + + // From the start + response = actor.onSubstring({ + start: 0, + end: 4 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING.substring(0, 4)); + + // In the middle + response = actor.onSubstring({ + start: 5, + end: 8 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING.substring(5, 8)); + + // Whole string + response = actor.onSubstring({ + start: 0, + end: TEST_STRING.length + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING); + + // Negative index + response = actor.onSubstring({ + start: -5, + end: TEST_STRING.length + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, + TEST_STRING.substring(-5, TEST_STRING.length)); + + // Past the end + response = actor.onSubstring({ + start: TEST_STRING.length - 5, + end: 100 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, + TEST_STRING.substring(TEST_STRING.length - 5, 100)); +} diff --git a/toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js new file mode 100644 index 000000000000..bdebd8578bf1 --- /dev/null +++ b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestGlobalClientAndResume(gClient, "test-grips", function(aResponse, aThreadClient) { + gThreadClient = aThreadClient; + test_longstring_grip(); + }); + }); + do_test_pending(); +} + +function test_longstring_grip() +{ + let longString = "All I want is to be a monkey of moderate intelligence who" + + " wears a suit... that's why I'm transferring to business school! Maybe I" + + " love you so much, I love you no matter who you are pretending to be." + + " Enough about your promiscuous mother, Hermes! We have bigger problems." + + " For example, if you killed your grandfather, you'd cease to exist! What" + + " kind of a father would I be if I said no? Yep, I remember. They came in" + + " last at the Olympics, then retired to promote alcoholic beverages! And" + + " remember, don't do anything that affects anything, unless it turns out" + + " you were supposed to, in which case, for the love of God, don't not do" + + " it!"; + + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) { + let args = aPacket.frame.arguments; + do_check_eq(args.length, 1); + let grip = args[0]; + + try { + do_check_eq(grip.type, "longString"); + do_check_eq(grip.length, longString.length); + do_check_eq(grip.initial, longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)); + + let longStringClient = gThreadClient.longString(grip); + longStringClient.substring(22, 28, function (aResponse) { + try { + do_check_eq(aResponse.substring, "monkey"); + } finally { + gThreadClient.resume(function() { + finishClient(gClient); + }); + } + }); + } catch(error) { + gThreadClient.resume(function() { + finishClient(gClient); + do_throw(error); + }); + } + }); + + gDebuggee.eval('stopMe("' + longString + '")'); +} + diff --git a/toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js new file mode 100644 index 000000000000..5c49ce059f3f --- /dev/null +++ b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestGlobalClientAndResume( + gClient, "test-grips", function(aResponse, aThreadClient) { + gThreadClient = aThreadClient; + test_longstring_grip(); + }); + }); + do_test_pending(); +} + +function test_longstring_grip() +{ + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) { + try { + let fakeLongStringGrip = { + type: "longString", + length: 1000000, + actor: "123fakeActor123", + initial: "" + }; + let longStringClient = gThreadClient.longString(fakeLongStringGrip); + longStringClient.substring(22, 28, function (aResponse) { + try { + do_check_true(!!aResponse.error, + "We should not get a response, but an error."); + } finally { + gThreadClient.resume(function() { + finishClient(gClient); + }); + } + }); + } catch(error) { + gThreadClient.resume(function() { + finishClient(gClient); + do_throw(error); + }); + } + }); + + gDebuggee.eval('stopMe()'); +} + diff --git a/toolkit/devtools/debugger/tests/unit/xpcshell.ini b/toolkit/devtools/debugger/tests/unit/xpcshell.ini index bd33026a637f..bd01b0013029 100644 --- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini +++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini @@ -60,4 +60,7 @@ tail = [test_framebindings-05.js] [test_pause_exceptions-01.js] [test_pause_exceptions-02.js] +[test_longstringactor.js] +[test_longstringgrips-01.js] +[test_longstringgrips-02.js] [test_breakpointstore.js]