mirror of https://github.com/axmolengine/axmol.git
issue #2823: firefox debugger step ok.
This commit is contained in:
parent
b5679cbc3a
commit
b5d02d8cca
|
@ -1 +1 @@
|
||||||
3959af89b6bbc8fb982769d22d2591991107d6e5
|
8ad5afb135018cfa93b3aee4dddbea24f0ef9b9e
|
|
@ -618,7 +618,6 @@ std::string FileUtils::fullPathForFilename(const std::string &filename)
|
||||||
|
|
||||||
for (auto searchIt = _searchPathArray.begin(); searchIt != _searchPathArray.end(); ++searchIt) {
|
for (auto searchIt = _searchPathArray.begin(); searchIt != _searchPathArray.end(); ++searchIt) {
|
||||||
for (auto resolutionIt = _searchResolutionsOrderArray.begin(); resolutionIt != _searchResolutionsOrderArray.end(); ++resolutionIt) {
|
for (auto resolutionIt = _searchResolutionsOrderArray.begin(); resolutionIt != _searchResolutionsOrderArray.end(); ++resolutionIt) {
|
||||||
|
|
||||||
fullpath = this->getPathForFilename(newFilename, *resolutionIt, *searchIt);
|
fullpath = this->getPathForFilename(newFilename, *resolutionIt, *searchIt);
|
||||||
|
|
||||||
if (fullpath.length() > 0)
|
if (fullpath.length() > 0)
|
||||||
|
|
|
@ -41,6 +41,9 @@ bool AppDelegate::applicationDidFinishLaunching()
|
||||||
// set FPS. the default value is 1.0/60 if you don't call this
|
// set FPS. the default value is 1.0/60 if you don't call this
|
||||||
pDirector->setAnimationInterval(1.0 / 60);
|
pDirector->setAnimationInterval(1.0 / 60);
|
||||||
|
|
||||||
|
FileUtils::getInstance()->addSearchPath("res");
|
||||||
|
FileUtils::getInstance()->addSearchPath("js");
|
||||||
|
|
||||||
ScriptingCore* sc = ScriptingCore::getInstance();
|
ScriptingCore* sc = ScriptingCore::getInstance();
|
||||||
sc->addRegisterCallback(register_all_cocos2dx);
|
sc->addRegisterCallback(register_all_cocos2dx);
|
||||||
sc->addRegisterCallback(register_all_cocos2dx_extension);
|
sc->addRegisterCallback(register_all_cocos2dx_extension);
|
||||||
|
@ -55,8 +58,6 @@ bool AppDelegate::applicationDidFinishLaunching()
|
||||||
|
|
||||||
sc->start();
|
sc->start();
|
||||||
|
|
||||||
FileUtils::getInstance()->addSearchPath("res");
|
|
||||||
|
|
||||||
auto pEngine = ScriptingCore::getInstance();
|
auto pEngine = ScriptingCore::getInstance();
|
||||||
ScriptEngineManager::getInstance()->setScriptEngine(pEngine);
|
ScriptEngineManager::getInstance()->setScriptEngine(pEngine);
|
||||||
#ifdef JS_OBFUSCATED
|
#ifdef JS_OBFUSCATED
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
381c282bed409f4b41a82505460d4d6c84ba70ab
|
0017c1d76ce42fd252ff8245774d5a276e278115
|
|
@ -662,9 +662,10 @@ JSBool ScriptingCore::executeScript(JSContext *cx, uint32_t argc, jsval *vp)
|
||||||
if (argc == 2 && argv[1].isString()) {
|
if (argc == 2 && argv[1].isString()) {
|
||||||
JSString* globalName = JSVAL_TO_STRING(argv[1]);
|
JSString* globalName = JSVAL_TO_STRING(argv[1]);
|
||||||
JSStringWrapper name(globalName);
|
JSStringWrapper name(globalName);
|
||||||
js::RootedObject* rootedGlobal = globals[name];
|
// js::RootedObject* rootedGlobal = globals[name];
|
||||||
if (rootedGlobal) {
|
JSObject* debugObj = ScriptingCore::getInstance()->getDebugGlobal();
|
||||||
res = ScriptingCore::getInstance()->runScript(path, rootedGlobal->get());
|
if (debugObj) {
|
||||||
|
res = ScriptingCore::getInstance()->runScript(path, debugObj);
|
||||||
} else {
|
} else {
|
||||||
JS_ReportError(cx, "Invalid global object: %s", (char*)name);
|
JS_ReportError(cx, "Invalid global object: %s", (char*)name);
|
||||||
return JS_FALSE;
|
return JS_FALSE;
|
||||||
|
@ -1965,8 +1966,8 @@ void ScriptingCore::enableDebugger() {
|
||||||
JS_DefineFunction(cx_, debugGlobal_, "_lockVM", JSBDebug_LockExecution, 2, JSPROP_READONLY | JSPROP_PERMANENT);
|
JS_DefineFunction(cx_, debugGlobal_, "_lockVM", JSBDebug_LockExecution, 2, JSPROP_READONLY | JSPROP_PERMANENT);
|
||||||
JS_DefineFunction(cx_, debugGlobal_, "_unlockVM", JSBDebug_UnlockExecution, 0, JSPROP_READONLY | JSPROP_PERMANENT);
|
JS_DefineFunction(cx_, debugGlobal_, "_unlockVM", JSBDebug_UnlockExecution, 0, JSPROP_READONLY | JSPROP_PERMANENT);
|
||||||
|
|
||||||
|
|
||||||
runScript("jsb_debugger.js", debugGlobal_);
|
runScript("jsb_debugger.js", debugGlobal_);
|
||||||
// runScript("SysTest/script.js", debugGlobal_);
|
|
||||||
|
|
||||||
CCLOG("before _prepareDebugger...");
|
CCLOG("before _prepareDebugger...");
|
||||||
// prepare the debugger
|
// prepare the debugger
|
||||||
|
@ -2266,7 +2267,6 @@ static void serverEntryPoint(void)
|
||||||
|
|
||||||
listen(s, 1);
|
listen(s, 1);
|
||||||
|
|
||||||
int recieveIndex = 0;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
clientSocket = accept(s, NULL, NULL);
|
clientSocket = accept(s, NULL, NULL);
|
||||||
|
|
||||||
|
@ -2284,144 +2284,13 @@ static void serverEntryPoint(void)
|
||||||
// process any input, send any output
|
// process any input, send any output
|
||||||
clearBuffers();
|
clearBuffers();
|
||||||
|
|
||||||
// if (recieveIndex == 0)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\"from\":\"root\",\"applicationType\":\"browser\",\"traits\":{\"sources\": true}}");
|
|
||||||
// ++recieveIndex;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
char buf[1024] = {0};
|
char buf[1024] = {0};
|
||||||
int readBytes = 0;
|
int readBytes = 0;
|
||||||
while ((readBytes = ::recv(clientSocket, buf, sizeof(buf), 0)) > 0)
|
while ((readBytes = ::recv(clientSocket, buf, sizeof(buf), 0)) > 0)
|
||||||
{
|
{
|
||||||
buf[readBytes] = '\0';
|
buf[readBytes] = '\0';
|
||||||
TRACE_DEBUGGER_SERVER("debug server : received command >%s", buf);
|
// TRACE_DEBUGGER_SERVER("debug server : received command >%s", buf);
|
||||||
|
|
||||||
// if (recieveIndex == 1)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{ \"from\":\"root\", \"tabs\":[{ \"actor\":\"JSBTabActor\", \"title\":\"Hello cocos2d-x JSB\", \"url\":\"http://www.cocos2d-x.org\" }], \"selected\":0 }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 2)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{ \"from\":\"JSBTabActor\", \"type\":\"tabAttached\", \"threadActor\":\"tabThreadActor111\" }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 3)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"tabThreadActor111\",\
|
|
||||||
// \"type\": \"paused\",\
|
|
||||||
// \"actor\": \"JSBTabActor\",\
|
|
||||||
// \"poppedFrames\": [],\
|
|
||||||
// \"why\": {\
|
|
||||||
// \"type\": \"attached\"\
|
|
||||||
// }\
|
|
||||||
// }");
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// //replyToClient(clientSocket, "{ \"from\":\"JSBTabActor\", \"type\":\"tabNavigated\", \"state\":\"start\", \"url\":\"my_url.js\" }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 4)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"tabThreadActor111\",\
|
|
||||||
// \"type\": \"newSource\",\
|
|
||||||
// \"source\": {\
|
|
||||||
// \"actor\": \"source_actor1\",\
|
|
||||||
// \"url\": \"file://~/Project/cocos2d-html5/cocos2d/CCDirector.js\",\
|
|
||||||
// \"isBlackBoxed\": false\
|
|
||||||
// }\
|
|
||||||
// }");
|
|
||||||
//
|
|
||||||
// replyToClient(clientSocket,
|
|
||||||
// "{\
|
|
||||||
// \"sources\": [\
|
|
||||||
// {\
|
|
||||||
// \"actor\": \"source_actor1\",\
|
|
||||||
// \"url\": \"file://~/Project/cocos2d-html5/cocos2d/CCDirector.js\",\
|
|
||||||
// \"isBlackBoxed\": false\
|
|
||||||
// }\
|
|
||||||
// ],\
|
|
||||||
// \"from\": \"tabThreadActor111\"\
|
|
||||||
// }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 5)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"source_actor1\",\
|
|
||||||
// \"source\": {\
|
|
||||||
// \"type\": \"longString\",\
|
|
||||||
// \"initial\": \"var cc = cc || {}; cc.Director = {};\",\
|
|
||||||
// \"length\": 100,\
|
|
||||||
// \"actor\": \"conn2.longString48\"\
|
|
||||||
// }\
|
|
||||||
// }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 6)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"conn2.longString48\",\
|
|
||||||
// \"substring\": \"var cc = cc || {}; cc.Director = {};\
|
|
||||||
//\\ncc.Sprite = {};\"\
|
|
||||||
// }");
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"tabThreadActor111\",\
|
|
||||||
// \"type\": \"resumed\"\
|
|
||||||
// }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 7)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"tabThreadActor111\",\
|
|
||||||
// \"type\": \"resumed\"\
|
|
||||||
// }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 8)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{ \"from\":\"tabThreadActor111\", \"actor\":\"breakActor\"}");//, \"actualLocation\":2 }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 9)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{ \"from\":\"breakActor\" }");
|
|
||||||
// }
|
|
||||||
// else if (recieveIndex == 10)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"tabThreadActor111\",\
|
|
||||||
// \"type\": \"resumed\"\
|
|
||||||
// }");
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// std::string recvBuf = buf;
|
|
||||||
// auto found = recvBuf.find("setBreakpoint");
|
|
||||||
// if (found != std::string::npos)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{ \"from\":\"tabThreadActor111\", \"actor\":\"breakActor\"}");//, \"actualLocation\":2 }");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// found = recvBuf.find("delete");
|
|
||||||
// if (found != std::string::npos)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{ \"from\":\"breakActor\" }");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// found = recvBuf.find("interrupt");
|
|
||||||
// if (found != std::string::npos)
|
|
||||||
// {
|
|
||||||
// replyToClient(clientSocket, "{\
|
|
||||||
// \"from\": \"tabThreadActor111\",\
|
|
||||||
// \"type\": \"resumed\"\
|
|
||||||
// }");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// ++recieveIndex;
|
|
||||||
// no other thread is using this
|
// no other thread is using this
|
||||||
inData.append(buf);
|
inData.append(buf);
|
||||||
// process any input, send any output
|
// process any input, send any output
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
db6aaa41d0756117258eebb759cd6420f66071e0
|
ceb78008acf831592a416d7dfa85a9dfd0ddae75
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var dump = function(msg) {
|
||||||
|
log(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* General utilities used throughout devtools. */
|
||||||
|
|
||||||
|
/* Turn the error e into a string, without fail. */
|
||||||
|
this.safeErrorString = function safeErrorString(aError) {
|
||||||
|
try {
|
||||||
|
var s = aError.toString();
|
||||||
|
if (typeof s === "string")
|
||||||
|
return s;
|
||||||
|
} catch (ee) { }
|
||||||
|
|
||||||
|
return "<failed trying to find error description>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report that |aWho| threw an exception, |aException|.
|
||||||
|
*/
|
||||||
|
this.reportException = function reportException(aWho, aException) {
|
||||||
|
let msg = aWho + " threw an exception: " + safeErrorString(aException);
|
||||||
|
if (aException.stack) {
|
||||||
|
msg += "\nCall stack:\n" + aException.stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
dump(msg + "\n");
|
||||||
|
|
||||||
|
// if (Components.utils.reportError) {
|
||||||
|
// /*
|
||||||
|
// * Note that the xpcshell test harness registers an observer for
|
||||||
|
// * console messages, so when we're running tests, this will cause
|
||||||
|
// * the test to quit.
|
||||||
|
// */
|
||||||
|
// Components.utils.reportError(msg);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a handler function that may throw, return an infallible handler
|
||||||
|
* function that calls the fallible handler, and logs any exceptions it
|
||||||
|
* throws.
|
||||||
|
*
|
||||||
|
* @param aHandler function
|
||||||
|
* A handler function, which may throw.
|
||||||
|
* @param aName string
|
||||||
|
* A name for aHandler, for use in error messages. If omitted, we use
|
||||||
|
* aHandler.name.
|
||||||
|
*
|
||||||
|
* (SpiderMonkey does generate good names for anonymous functions, but we
|
||||||
|
* don't have a way to get at them from JavaScript at the moment.)
|
||||||
|
*/
|
||||||
|
this.makeInfallible = function makeInfallible(aHandler, aName) {
|
||||||
|
if (!aName)
|
||||||
|
aName = aHandler.name;
|
||||||
|
|
||||||
|
return function (/* arguments */) {
|
||||||
|
try {
|
||||||
|
return aHandler.apply(this, arguments);
|
||||||
|
} catch (ex) {
|
||||||
|
let who = "Handler function";
|
||||||
|
if (aName) {
|
||||||
|
who += " " + aName;
|
||||||
|
}
|
||||||
|
reportException(who, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
|
||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||||
|
|
||||||
|
let { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||||
|
"resource://gre/modules/devtools/Console.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "CommandUtils",
|
||||||
|
"resource:///modules/devtools/DeveloperToolbar.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "require", function() {
|
||||||
|
let { require } = Cu.import("resource://gre/modules/devtools/Require.jsm", {});
|
||||||
|
Cu.import("resource://gre/modules/devtools/gcli.jsm", {});
|
||||||
|
return require;
|
||||||
|
});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "canon", () => require("gcli/canon"));
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "Requisition", () => require("gcli/cli").Requisition);
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "util", () => require("util/util"));
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage remote connections that want to talk to GCLI
|
||||||
|
* @constructor
|
||||||
|
* @param connection The connection to the client, DebuggerServerConnection
|
||||||
|
* @param parentActor Optional, the parent actor
|
||||||
|
*/
|
||||||
|
function GcliActor(connection, parentActor) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
GcliActor.prototype.actorPrefix = "gcli";
|
||||||
|
|
||||||
|
GcliActor.prototype.disconnect = function() {
|
||||||
|
};
|
||||||
|
|
||||||
|
GcliActor.prototype.getCommandSpecs = function(request) {
|
||||||
|
return { commandSpecs: canon.getCommandSpecs() };
|
||||||
|
};
|
||||||
|
|
||||||
|
GcliActor.prototype.execute = function(request) {
|
||||||
|
let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
|
||||||
|
.getService(Ci.nsIWindowMediator);
|
||||||
|
let chromeWindow = windowMediator.getMostRecentWindow("navigator:browser");
|
||||||
|
let contentWindow = chromeWindow.gBrowser.selectedTab.linkedBrowser.contentWindow;
|
||||||
|
|
||||||
|
let environment = CommandUtils.createEnvironment(chromeWindow.document,
|
||||||
|
contentWindow.document);
|
||||||
|
|
||||||
|
let requisition = new Requisition(environment);
|
||||||
|
requisition.updateExec(request.typed).then(output => {
|
||||||
|
return output.promise.then(() => {
|
||||||
|
this.connection.send({
|
||||||
|
from: this.actorID,
|
||||||
|
requestId: request.requestId,
|
||||||
|
data: output.data,
|
||||||
|
type: output.type,
|
||||||
|
error: output.error
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(null, console.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
GcliActor.prototype.requestTypes = {
|
||||||
|
getCommandSpecs: GcliActor.prototype.getCommandSpecs,
|
||||||
|
execute: GcliActor.prototype.execute,
|
||||||
|
};
|
||||||
|
|
||||||
|
addTabActor(GcliActor, "gcliActor");
|
||||||
|
addGlobalActor(GcliActor, "gcliActor");
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,218 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var startedProfilers = 0;
|
||||||
|
var startTime = 0;
|
||||||
|
|
||||||
|
function getCurrentTime() {
|
||||||
|
return (new Date()).getTime() - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ProfilerActor. ProfilerActor provides remote access to the
|
||||||
|
* built-in profiler module.
|
||||||
|
*/
|
||||||
|
function ProfilerActor(aConnection)
|
||||||
|
{
|
||||||
|
this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||||
|
this._started = false;
|
||||||
|
this._observedEvents = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerActor.prototype = {
|
||||||
|
actorPrefix: "profiler",
|
||||||
|
|
||||||
|
disconnect: function() {
|
||||||
|
for (var event of this._observedEvents) {
|
||||||
|
Services.obs.removeObserver(this, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stopProfiler();
|
||||||
|
this._profiler = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
stopProfiler: function() {
|
||||||
|
// We stop the profiler only after the last client has
|
||||||
|
// stopped profiling. Otherwise there's a problem where
|
||||||
|
// we stop the profiler as soon as you close the devtools
|
||||||
|
// panel in one tab even though there might be other
|
||||||
|
// profiler instances running in other tabs.
|
||||||
|
if (!this._started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._started = false;
|
||||||
|
startedProfilers -= 1;
|
||||||
|
if (startedProfilers <= 0) {
|
||||||
|
this._profiler.StopProfiler();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onStartProfiler: function(aRequest) {
|
||||||
|
this._profiler.StartProfiler(aRequest.entries, aRequest.interval,
|
||||||
|
aRequest.features, aRequest.features.length);
|
||||||
|
this._started = true;
|
||||||
|
startedProfilers += 1;
|
||||||
|
startTime = (new Date()).getTime();
|
||||||
|
return { "msg": "profiler started" }
|
||||||
|
},
|
||||||
|
onStopProfiler: function(aRequest) {
|
||||||
|
this.stopProfiler();
|
||||||
|
return { "msg": "profiler stopped" }
|
||||||
|
},
|
||||||
|
onGetProfileStr: function(aRequest) {
|
||||||
|
var profileStr = this._profiler.GetProfile();
|
||||||
|
return { "profileStr": profileStr }
|
||||||
|
},
|
||||||
|
onGetProfile: function(aRequest) {
|
||||||
|
var profile = this._profiler.getProfileData();
|
||||||
|
return { "profile": profile, "currentTime": getCurrentTime() }
|
||||||
|
},
|
||||||
|
onIsActive: function(aRequest) {
|
||||||
|
var isActive = this._profiler.IsActive();
|
||||||
|
var currentTime = isActive ? getCurrentTime() : null;
|
||||||
|
return { "isActive": isActive, "currentTime": currentTime }
|
||||||
|
},
|
||||||
|
onGetResponsivenessTimes: function(aRequest) {
|
||||||
|
var times = this._profiler.GetResponsivenessTimes({});
|
||||||
|
return { "responsivenessTimes": times }
|
||||||
|
},
|
||||||
|
onGetFeatures: function(aRequest) {
|
||||||
|
var features = this._profiler.GetFeatures([]);
|
||||||
|
return { "features": features }
|
||||||
|
},
|
||||||
|
onGetSharedLibraryInformation: function(aRequest) {
|
||||||
|
var sharedLibraries = this._profiler.getSharedLibraryInformation();
|
||||||
|
return { "sharedLibraryInformation": sharedLibraries }
|
||||||
|
},
|
||||||
|
onRegisterEventNotifications: function(aRequest) {
|
||||||
|
let registered = [];
|
||||||
|
for (var event of aRequest.events) {
|
||||||
|
if (this._observedEvents.indexOf(event) != -1)
|
||||||
|
continue;
|
||||||
|
Services.obs.addObserver(this, event, false);
|
||||||
|
this._observedEvents.push(event);
|
||||||
|
registered.push(event);
|
||||||
|
}
|
||||||
|
return { registered: registered }
|
||||||
|
},
|
||||||
|
onUnregisterEventNotifications: function(aRequest) {
|
||||||
|
let unregistered = [];
|
||||||
|
for (var event of aRequest.events) {
|
||||||
|
let idx = this._observedEvents.indexOf(event);
|
||||||
|
if (idx == -1)
|
||||||
|
continue;
|
||||||
|
Services.obs.removeObserver(this, event);
|
||||||
|
this._observedEvents.splice(idx, 1);
|
||||||
|
unregistered.push(event);
|
||||||
|
}
|
||||||
|
return { unregistered: unregistered }
|
||||||
|
},
|
||||||
|
observe: makeInfallible(function(aSubject, aTopic, aData) {
|
||||||
|
/*
|
||||||
|
* this.conn.send can only transmit acyclic values. However, it is
|
||||||
|
* idiomatic for wrapped JS objects like aSubject (and possibly aData?)
|
||||||
|
* to have a 'wrappedJSObject' property pointing to themselves.
|
||||||
|
*
|
||||||
|
* this.conn.send also assumes that it can retain the object it is
|
||||||
|
* passed to be handled on later event ticks; and that it's okay to
|
||||||
|
* freeze it. Since we don't really know what aSubject and aData are,
|
||||||
|
* we need to pass this.conn.send a copy of them, not the originals.
|
||||||
|
*
|
||||||
|
* We break the cycle and make the copy by JSON.stringifying those
|
||||||
|
* values with a replacer that omits properties known to introduce
|
||||||
|
* cycles, and then JSON.parsing the result. This spends processor
|
||||||
|
* time, but it's simple.
|
||||||
|
*/
|
||||||
|
function cycleBreaker(key, value) {
|
||||||
|
if (key === 'wrappedJSObject') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If these values are objects with a non-null 'wrappedJSObject'
|
||||||
|
* property, use its value. Otherwise, use the value unchanged.
|
||||||
|
*/
|
||||||
|
aSubject = (aSubject && aSubject.wrappedJSObject) || aSubject;
|
||||||
|
aData = (aData && aData.wrappedJSObject) || aData;
|
||||||
|
|
||||||
|
let subj = JSON.parse(JSON.stringify(aSubject, cycleBreaker));
|
||||||
|
let data = JSON.parse(JSON.stringify(aData, cycleBreaker));
|
||||||
|
|
||||||
|
let send = (extra) => {
|
||||||
|
data = data || {};
|
||||||
|
|
||||||
|
if (extra)
|
||||||
|
data.extra = extra;
|
||||||
|
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "eventNotification",
|
||||||
|
event: aTopic,
|
||||||
|
subject: subj,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aTopic !== "console-api-profiler")
|
||||||
|
return void send();
|
||||||
|
|
||||||
|
// If the event was generated from console.profile or
|
||||||
|
// console.profileEnd we need to start the profiler
|
||||||
|
// right away and only then notify our client. Otherwise,
|
||||||
|
// we'll lose precious samples.
|
||||||
|
|
||||||
|
let name = subj.arguments[0];
|
||||||
|
|
||||||
|
if (subj.action === "profile") {
|
||||||
|
let resp = this.onIsActive();
|
||||||
|
|
||||||
|
if (resp.isActive) {
|
||||||
|
return void send({
|
||||||
|
name: name,
|
||||||
|
currentTime: resp.currentTime,
|
||||||
|
action: "profile"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onStartProfiler({
|
||||||
|
entries: 1000000,
|
||||||
|
interval: 1,
|
||||||
|
features: ["js"]
|
||||||
|
});
|
||||||
|
|
||||||
|
return void send({ currentTime: 0, action: "profile", name: name });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subj.action === "profileEnd") {
|
||||||
|
let resp = this.onGetProfile();
|
||||||
|
resp.action = "profileEnd";
|
||||||
|
resp.name = name;
|
||||||
|
send(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined; // Otherwise xpcshell tests fail.
|
||||||
|
}, "ProfilerActor.prototype.observe"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request types this actor can handle.
|
||||||
|
*/
|
||||||
|
ProfilerActor.prototype.requestTypes = {
|
||||||
|
"startProfiler": ProfilerActor.prototype.onStartProfiler,
|
||||||
|
"stopProfiler": ProfilerActor.prototype.onStopProfiler,
|
||||||
|
"getProfileStr": ProfilerActor.prototype.onGetProfileStr,
|
||||||
|
"getProfile": ProfilerActor.prototype.onGetProfile,
|
||||||
|
"isActive": ProfilerActor.prototype.onIsActive,
|
||||||
|
"getResponsivenessTimes": ProfilerActor.prototype.onGetResponsivenessTimes,
|
||||||
|
"getFeatures": ProfilerActor.prototype.onGetFeatures,
|
||||||
|
"getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation,
|
||||||
|
"registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications,
|
||||||
|
"unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerServer.addGlobalActor(ProfilerActor, "profilerActor");
|
|
@ -0,0 +1,329 @@
|
||||||
|
/* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/* Root actor for the remote debugging protocol. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods shared between RootActor and BrowserTabActor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate |this._extraActors| as specified by |aFactories|, reusing whatever
|
||||||
|
* actors are already there. Add all actors in the final extra actors table to
|
||||||
|
* |aPool|.
|
||||||
|
*
|
||||||
|
* The root actor and the tab actor use this to instantiate actors that other
|
||||||
|
* parts of the browser have specified with DebuggerServer.addTabActor antd
|
||||||
|
* DebuggerServer.addGlobalActor.
|
||||||
|
*
|
||||||
|
* @param aFactories
|
||||||
|
* An object whose own property names are the names of properties to add to
|
||||||
|
* some reply packet (say, a tab actor grip or the "listTabs" response
|
||||||
|
* form), and whose own property values are actor constructor functions, as
|
||||||
|
* documented for addTabActor and addGlobalActor.
|
||||||
|
*
|
||||||
|
* @param this
|
||||||
|
* The BrowserRootActor or BrowserTabActor with which the new actors will
|
||||||
|
* be associated. It should support whatever API the |aFactories|
|
||||||
|
* constructor functions might be interested in, as it is passed to them.
|
||||||
|
* For the sake of CommonCreateExtraActors itself, it should have at least
|
||||||
|
* the following properties:
|
||||||
|
*
|
||||||
|
* - _extraActors
|
||||||
|
* An object whose own property names are factory table (and packet)
|
||||||
|
* property names, and whose values are no-argument actor constructors,
|
||||||
|
* of the sort that one can add to an ActorPool.
|
||||||
|
*
|
||||||
|
* - conn
|
||||||
|
* The DebuggerServerConnection in which the new actors will participate.
|
||||||
|
*
|
||||||
|
* - actorID
|
||||||
|
* The actor's name, for use as the new actors' parentID.
|
||||||
|
*/
|
||||||
|
function CommonCreateExtraActors(aFactories, aPool) {
|
||||||
|
// Walk over global actors added by extensions.
|
||||||
|
for (let name in aFactories) {
|
||||||
|
let actor = this._extraActors[name];
|
||||||
|
if (!actor) {
|
||||||
|
actor = aFactories[name].bind(null, this.conn, this);
|
||||||
|
actor.prototype = aFactories[name].prototype;
|
||||||
|
actor.parentID = this.actorID;
|
||||||
|
this._extraActors[name] = actor;
|
||||||
|
}
|
||||||
|
aPool.addActor(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the extra actors in |this._extraActors|, constructed by a prior call
|
||||||
|
* to CommonCreateExtraActors, to |aObject|.
|
||||||
|
*
|
||||||
|
* @param aObject
|
||||||
|
* The object to which the extra actors should be added, under the
|
||||||
|
* property names given in the |aFactories| table passed to
|
||||||
|
* CommonCreateExtraActors.
|
||||||
|
*
|
||||||
|
* @param this
|
||||||
|
* The BrowserRootActor or BrowserTabActor whose |_extraActors| table we
|
||||||
|
* should use; see above.
|
||||||
|
*/
|
||||||
|
function CommonAppendExtraActors(aObject) {
|
||||||
|
for (let name in this._extraActors) {
|
||||||
|
let actor = this._extraActors[name];
|
||||||
|
aObject[name] = actor.actorID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a remote debugging protocol root actor.
|
||||||
|
*
|
||||||
|
* @param aConnection
|
||||||
|
* The DebuggerServerConnection whose root actor we are constructing.
|
||||||
|
*
|
||||||
|
* @param aParameters
|
||||||
|
* The properties of |aParameters| provide backing objects for the root
|
||||||
|
* actor's requests; if a given property is omitted from |aParameters|, the
|
||||||
|
* root actor won't implement the corresponding requests or notifications.
|
||||||
|
* Supported properties:
|
||||||
|
*
|
||||||
|
* - tabList: a live list (see below) of tab actors. If present, the
|
||||||
|
* new root actor supports the 'listTabs' request, providing the live
|
||||||
|
* list's elements as its tab actors, and sending 'tabListChanged'
|
||||||
|
* notifications when the live list's contents change. One actor in
|
||||||
|
* this list must have a true '.selected' property.
|
||||||
|
*
|
||||||
|
* - globalActorFactories: an object |A| describing further actors to
|
||||||
|
* attach to the 'listTabs' reply. This is the type accumulated by
|
||||||
|
* DebuggerServer.addGlobalActor. For each own property |P| of |A|,
|
||||||
|
* the root actor adds a property named |P| to the 'listTabs'
|
||||||
|
* reply whose value is the name of an actor constructed by
|
||||||
|
* |A[P]|.
|
||||||
|
*
|
||||||
|
* - onShutdown: a function to call when the root actor is disconnected.
|
||||||
|
*
|
||||||
|
* Instance properties:
|
||||||
|
*
|
||||||
|
* - applicationType: the string the root actor will include as the
|
||||||
|
* "applicationType" property in the greeting packet. By default, this
|
||||||
|
* is "browser".
|
||||||
|
*
|
||||||
|
* Live lists:
|
||||||
|
*
|
||||||
|
* A "live list", as used for the |tabList|, is an object that presents a
|
||||||
|
* list of actors, and also notifies its clients of changes to the list. A
|
||||||
|
* live list's interface is two properties:
|
||||||
|
*
|
||||||
|
* - iterator: a method that returns an iterator. A for-of loop will call
|
||||||
|
* this method to obtain an iterator for the loop, so if LL is
|
||||||
|
* a live list, one can simply write 'for (i of LL) ...'.
|
||||||
|
*
|
||||||
|
* - onListChanged: a handler called, with no arguments, when the set of
|
||||||
|
* values the iterator would produce has changed since the last
|
||||||
|
* time 'iterator' was called. This may only be set to null or a
|
||||||
|
* callable value (one for which the typeof operator returns
|
||||||
|
* 'function'). (Note that the live list will not call the
|
||||||
|
* onListChanged handler until the list has been iterated over
|
||||||
|
* once; if nobody's seen the list in the first place, nobody
|
||||||
|
* should care if its contents have changed!)
|
||||||
|
*
|
||||||
|
* When the list changes, the list implementation should ensure that any
|
||||||
|
* actors yielded in previous iterations whose referents (tabs) still exist
|
||||||
|
* get yielded again in subsequent iterations. If the underlying referent
|
||||||
|
* is the same, the same actor should be presented for it.
|
||||||
|
*
|
||||||
|
* The root actor registers an 'onListChanged' handler on the appropriate
|
||||||
|
* list when it may need to send the client 'tabListChanged' notifications,
|
||||||
|
* and is careful to remove the handler whenever it does not need to send
|
||||||
|
* such notifications (including when it is disconnected). This means that
|
||||||
|
* live list implementations can use the state of the handler property (set
|
||||||
|
* or null) to install and remove observers and event listeners.
|
||||||
|
*
|
||||||
|
* Note that, as the only way for the root actor to see the members of the
|
||||||
|
* live list is to begin an iteration over the list, the live list need not
|
||||||
|
* actually produce any actors until they are reached in the course of
|
||||||
|
* iteration: alliterative lazy live lists.
|
||||||
|
*/
|
||||||
|
function RootActor(aConnection, aParameters) {
|
||||||
|
this.conn = aConnection;
|
||||||
|
this._parameters = aParameters;
|
||||||
|
this._onTabListChanged = this.onTabListChanged.bind(this);
|
||||||
|
this._extraActors = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
RootActor.prototype = {
|
||||||
|
constructor: RootActor,
|
||||||
|
applicationType: "browser",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
|
||||||
|
*/
|
||||||
|
sayHello: function() {
|
||||||
|
return {
|
||||||
|
from: "root",
|
||||||
|
applicationType: this.applicationType,
|
||||||
|
/* This is not in the spec, but it's used by tests. */
|
||||||
|
testConnectionPrefix: this.conn.prefix,
|
||||||
|
traits: {
|
||||||
|
sources: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the actor from the browser window.
|
||||||
|
*/
|
||||||
|
disconnect: function() {
|
||||||
|
/* Tell the live lists we aren't watching any more. */
|
||||||
|
if (this._parameters.tabList) {
|
||||||
|
this._parameters.tabList.onListChanged = null;
|
||||||
|
}
|
||||||
|
if (typeof this._parameters.onShutdown === 'function') {
|
||||||
|
this._parameters.onShutdown();
|
||||||
|
}
|
||||||
|
this._extraActors = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/* The 'listTabs' request and the 'tabListChanged' notification. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the listTabs request. The actors will survive until at least
|
||||||
|
* the next listTabs request.
|
||||||
|
*/
|
||||||
|
onListTabs: function() {
|
||||||
|
|
||||||
|
let tabList = this._parameters.tabList;
|
||||||
|
if (!tabList) {
|
||||||
|
return { from: "root", error: "noTabs",
|
||||||
|
message: "This root actor has no browser tabs." };
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Walk the tab list, accumulating the array of tab actors for the
|
||||||
|
* reply, and moving all the actors to a new ActorPool. We'll
|
||||||
|
* replace the old tab actor pool with the one we build here, thus
|
||||||
|
* retiring any actors that didn't get listed again, and preparing any
|
||||||
|
* new actors to receive packets.
|
||||||
|
*/
|
||||||
|
let newActorPool = new ActorPool(this.conn);
|
||||||
|
let tabActorList = [];
|
||||||
|
let selected;
|
||||||
|
for (let tabActor of tabList) {
|
||||||
|
if (tabActor.selected) {
|
||||||
|
selected = tabActorList.length;
|
||||||
|
}
|
||||||
|
tabActor.parentID = this.actorID;
|
||||||
|
newActorPool.addActor(tabActor);
|
||||||
|
tabActorList.push(tabActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DebuggerServer.addGlobalActor support: create actors. */
|
||||||
|
this._createExtraActors(this._parameters.globalActorFactories, newActorPool);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drop the old actorID -> actor map. Actors that still mattered were
|
||||||
|
* added to the new map; others will go away.
|
||||||
|
*/
|
||||||
|
if (this._tabActorPool) {
|
||||||
|
this.conn.removeActorPool(this._tabActorPool);
|
||||||
|
}
|
||||||
|
this._tabActorPool = newActorPool;
|
||||||
|
this.conn.addActorPool(this._tabActorPool);
|
||||||
|
|
||||||
|
let reply = {
|
||||||
|
"from": "root",
|
||||||
|
"selected": selected || 0,
|
||||||
|
"tabs": [actor.grip() for (actor of tabActorList)]
|
||||||
|
};
|
||||||
|
|
||||||
|
/* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */
|
||||||
|
this._appendExtraActors(reply);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now that we're actually going to report the contents of tabList to
|
||||||
|
* the client, we're responsible for letting the client know if it
|
||||||
|
* changes.
|
||||||
|
*/
|
||||||
|
tabList.onListChanged = this._onTabListChanged;
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
},
|
||||||
|
|
||||||
|
onTabListChanged: function () {
|
||||||
|
this.conn.send({ from:"root", type:"tabListChanged" });
|
||||||
|
/* It's a one-shot notification; no need to watch any more. */
|
||||||
|
this._parameters.tabList.onListChanged = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/* This is not in the spec, but it's used by tests. */
|
||||||
|
onEcho: (aRequest) => aRequest,
|
||||||
|
|
||||||
|
/* Support for DebuggerServer.addGlobalActor. */
|
||||||
|
_createExtraActors: CommonCreateExtraActors,
|
||||||
|
_appendExtraActors: CommonAppendExtraActors,
|
||||||
|
|
||||||
|
/* ThreadActor hooks. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare to enter a nested event loop by disabling debuggee events.
|
||||||
|
*/
|
||||||
|
preNest: function() {
|
||||||
|
// Disable events in all open windows.
|
||||||
|
let e = windowMediator.getEnumerator(null);
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
let win = e.getNext();
|
||||||
|
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
windowUtils.suppressEventHandling(true);
|
||||||
|
windowUtils.suspendTimeouts();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare to exit a nested event loop by enabling debuggee events.
|
||||||
|
*/
|
||||||
|
postNest: function(aNestData) {
|
||||||
|
// Enable events in all open windows.
|
||||||
|
let e = windowMediator.getEnumerator(null);
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
let win = e.getNext();
|
||||||
|
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
windowUtils.resumeTimeouts();
|
||||||
|
windowUtils.suppressEventHandling(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ChromeDebuggerActor hooks. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified actor to the default actor pool connection, in order to
|
||||||
|
* keep it alive as long as the server is. This is used by breakpoints in the
|
||||||
|
* thread and chrome debugger actors.
|
||||||
|
*
|
||||||
|
* @param actor aActor
|
||||||
|
* The actor object.
|
||||||
|
*/
|
||||||
|
addToParentPool: function(aActor) {
|
||||||
|
this.conn.addActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified actor from the default actor pool.
|
||||||
|
*
|
||||||
|
* @param BreakpointActor aActor
|
||||||
|
* The actor object.
|
||||||
|
*/
|
||||||
|
removeFromParentPool: function(aActor) {
|
||||||
|
this.conn.removeActor(aActor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RootActor.prototype.requestTypes = {
|
||||||
|
"listTabs": RootActor.prototype.onListTabs,
|
||||||
|
"echo": RootActor.prototype.onEcho
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,145 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let {Cu} = require("chrome");
|
||||||
|
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||||
|
|
||||||
|
let promise = require("sdk/core/promise");
|
||||||
|
let {Class} = require("sdk/core/heritage");
|
||||||
|
|
||||||
|
let protocol = require("devtools/server/protocol");
|
||||||
|
let {method, Arg, Option, RetVal} = protocol;
|
||||||
|
|
||||||
|
exports.LongStringActor = protocol.ActorClass({
|
||||||
|
typeName: "longstractor",
|
||||||
|
|
||||||
|
initialize: function(conn, str) {
|
||||||
|
protocol.Actor.prototype.initialize.call(this, conn);
|
||||||
|
this.str = str;
|
||||||
|
this.short = (this.str.length < DebuggerServer.LONG_STRING_LENGTH);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this.str = null;
|
||||||
|
protocol.Actor.prototype.destroy.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
form: function() {
|
||||||
|
if (this.short) {
|
||||||
|
return this.str;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: "longString",
|
||||||
|
actor: this.actorID,
|
||||||
|
length: this.str.length,
|
||||||
|
initial: this.str.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
substring: method(function(start, end) {
|
||||||
|
return promise.resolve(this.str.substring(start, end));
|
||||||
|
}, {
|
||||||
|
request: {
|
||||||
|
start: Arg(0),
|
||||||
|
end: Arg(1)
|
||||||
|
},
|
||||||
|
response: { substring: RetVal() },
|
||||||
|
}),
|
||||||
|
|
||||||
|
release: method(function() { }, { release: true })
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a LongString on the server is short enough to be passed
|
||||||
|
* as a full string, the client will get a ShortLongString instead of
|
||||||
|
* a LongStringFront. Its API should match.
|
||||||
|
*
|
||||||
|
* I'm very proud of this name.
|
||||||
|
*/
|
||||||
|
exports.ShortLongString = Class({
|
||||||
|
initialize: function(str) {
|
||||||
|
this.str = str;
|
||||||
|
},
|
||||||
|
|
||||||
|
get length() { return this.str.length; },
|
||||||
|
get initial() { return this.str; },
|
||||||
|
string: function() { return promise.resolve(this.str) },
|
||||||
|
|
||||||
|
substring: function(start, end) {
|
||||||
|
return promise.resolve(this.str.substring(start, end));
|
||||||
|
},
|
||||||
|
|
||||||
|
release: function() {
|
||||||
|
this.str = null;
|
||||||
|
return promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
exports.LongStringFront = protocol.FrontClass(exports.LongStringActor, {
|
||||||
|
initialize: function(client, form) {
|
||||||
|
// Don't give the form by default, because we're being tricky and it might just
|
||||||
|
// be a string.
|
||||||
|
protocol.Front.prototype.initialize.call(this, client, null);
|
||||||
|
this.form(form);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this.initial = null;
|
||||||
|
this.length = null;
|
||||||
|
this.strPromise = null;
|
||||||
|
protocol.Front.prototype.destroy.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
form: function(form) {
|
||||||
|
this.actorID = form.actorID;
|
||||||
|
this.initial = form.initial;
|
||||||
|
this.length = form.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
string: function() {
|
||||||
|
if (!this.strPromise) {
|
||||||
|
let promiseRest = (thusFar) => {
|
||||||
|
if (thusFar.length === this.length)
|
||||||
|
return promise.resolve(thusFar);
|
||||||
|
else {
|
||||||
|
return this.substring(thusFar.length,
|
||||||
|
thusFar.length + DebuggerServer.LONG_STRING_READ_LENGTH)
|
||||||
|
.then((next) => promiseRest(thusFar + next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.strPromise = promiseRest(this.initial);
|
||||||
|
}
|
||||||
|
return this.strPromise;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The long string actor needs some custom marshalling, because it is sometimes
|
||||||
|
// returned as a primitive rather than a complete form.
|
||||||
|
|
||||||
|
let stringActorType = protocol.types.getType("longstractor");
|
||||||
|
protocol.types.addType("longstring", {
|
||||||
|
_actor: true,
|
||||||
|
write: (value, context, detail) => {
|
||||||
|
if (!(context instanceof protocol.Actor)) {
|
||||||
|
throw Error("Passing a longstring as an argument isn't supported.");
|
||||||
|
}
|
||||||
|
if (value.short) {
|
||||||
|
return value.str;
|
||||||
|
} else {
|
||||||
|
return stringActorType.write(value, context, detail);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
read: (value, context, detail) => {
|
||||||
|
if (context instanceof protocol.Actor) {
|
||||||
|
throw Error("Passing a longstring as an argument isn't supported.");
|
||||||
|
}
|
||||||
|
if (typeof(value) === "string") {
|
||||||
|
return exports.ShortLongString(value);
|
||||||
|
}
|
||||||
|
return stringActorType.read(value, context, detail);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,740 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let Cc = Components.classes;
|
||||||
|
let Ci = Components.interfaces;
|
||||||
|
let Cu = Components.utils;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||||
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||||
|
|
||||||
|
let TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
||||||
|
let TRANSITION_DURATION_MS = 500;
|
||||||
|
let TRANSITION_RULE = "\
|
||||||
|
:root.moz-styleeditor-transitioning, :root.moz-styleeditor-transitioning * {\
|
||||||
|
transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
|
||||||
|
transition-delay: 0ms !important;\
|
||||||
|
transition-timing-function: ease-out !important;\
|
||||||
|
transition-property: all !important;\
|
||||||
|
}";
|
||||||
|
|
||||||
|
let LOAD_ERROR = "error-load";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a StyleEditorActor. StyleEditorActor provides remote access to the
|
||||||
|
* built-in style editor module.
|
||||||
|
*/
|
||||||
|
function StyleEditorActor(aConnection, aParentActor)
|
||||||
|
{
|
||||||
|
this.conn = aConnection;
|
||||||
|
this._onDocumentLoaded = this._onDocumentLoaded.bind(this);
|
||||||
|
this._onSheetLoaded = this._onSheetLoaded.bind(this);
|
||||||
|
|
||||||
|
if (aParentActor instanceof BrowserTabActor &&
|
||||||
|
aParentActor.browser instanceof Ci.nsIDOMWindow) {
|
||||||
|
this._window = aParentActor.browser;
|
||||||
|
}
|
||||||
|
else if (aParentActor instanceof BrowserTabActor &&
|
||||||
|
aParentActor.browser instanceof Ci.nsIDOMElement) {
|
||||||
|
this._window = aParentActor.browser.contentWindow;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._window = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep a map of sheets-to-actors so we don't create two actors for one sheet
|
||||||
|
this._sheets = new Map();
|
||||||
|
|
||||||
|
this._actorPool = new ActorPool(this.conn);
|
||||||
|
this.conn.addActorPool(this._actorPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleEditorActor.prototype = {
|
||||||
|
/**
|
||||||
|
* Actor pool for all of the actors we send to the client.
|
||||||
|
*/
|
||||||
|
_actorPool: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The debugger server connection instance.
|
||||||
|
*/
|
||||||
|
conn: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content window we work with.
|
||||||
|
*/
|
||||||
|
get win() this._window,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current content document of the window we work with.
|
||||||
|
*/
|
||||||
|
get doc() this._window.document,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A window object, usually the browser window
|
||||||
|
*/
|
||||||
|
_window: null,
|
||||||
|
|
||||||
|
actorPrefix: "styleEditor",
|
||||||
|
|
||||||
|
form: function()
|
||||||
|
{
|
||||||
|
return { actor: this.actorID };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the current StyleEditorActor instance.
|
||||||
|
*/
|
||||||
|
disconnect: function()
|
||||||
|
{
|
||||||
|
if (this._observer) {
|
||||||
|
this._observer.disconnect();
|
||||||
|
delete this._observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sheets.clear();
|
||||||
|
|
||||||
|
this.conn.removeActorPool(this._actorPool);
|
||||||
|
this._actorPool = null;
|
||||||
|
this.conn = this._window = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release an actor from our actor pool.
|
||||||
|
*/
|
||||||
|
releaseActor: function(actor)
|
||||||
|
{
|
||||||
|
if (this._actorPool) {
|
||||||
|
this._actorPool.removeActor(actor.actorID);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the BaseURI for the document.
|
||||||
|
*
|
||||||
|
* @return {object} JSON message to with BaseURI
|
||||||
|
*/
|
||||||
|
onGetBaseURI: function() {
|
||||||
|
return { baseURI: this.doc.baseURIObject };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when target navigates to a new document.
|
||||||
|
* Adds load listeners to document.
|
||||||
|
*/
|
||||||
|
onNewDocument: function() {
|
||||||
|
// delete previous document's actors
|
||||||
|
this._clearStyleSheetActors();
|
||||||
|
|
||||||
|
// Note: listening for load won't be necessary once
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed
|
||||||
|
if (this.doc.readyState == "complete") {
|
||||||
|
this._onDocumentLoaded();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.win.addEventListener("load", this._onDocumentLoaded, false);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for document loaded event. Add actor for each stylesheet
|
||||||
|
* and send an event notifying of the load
|
||||||
|
*/
|
||||||
|
_onDocumentLoaded: function(event) {
|
||||||
|
if (event) {
|
||||||
|
this.win.removeEventListener("load", this._onDocumentLoaded, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let documents = [this.doc];
|
||||||
|
var forms = [];
|
||||||
|
for (let doc of documents) {
|
||||||
|
let sheetForms = this._addStyleSheets(doc.styleSheets);
|
||||||
|
forms = forms.concat(sheetForms);
|
||||||
|
// Recursively handle style sheets of the documents in iframes.
|
||||||
|
for (let iframe of doc.getElementsByTagName("iframe")) {
|
||||||
|
documents.push(iframe.contentDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "documentLoad",
|
||||||
|
styleSheets: forms
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the stylesheets to the map and create an actor
|
||||||
|
* for each one if not already created. Send event that there
|
||||||
|
* are new stylesheets.
|
||||||
|
*
|
||||||
|
* @param {[DOMStyleSheet]} styleSheets
|
||||||
|
* Stylesheets to add
|
||||||
|
* @return {[object]}
|
||||||
|
* Array of forms for each StyleSheetActor created
|
||||||
|
*/
|
||||||
|
_addStyleSheets: function(styleSheets)
|
||||||
|
{
|
||||||
|
let sheets = [];
|
||||||
|
for (let i = 0; i < styleSheets.length; i++) {
|
||||||
|
let styleSheet = styleSheets[i];
|
||||||
|
sheets.push(styleSheet);
|
||||||
|
|
||||||
|
// Get all sheets, including imported ones
|
||||||
|
let imports = this._getImported(styleSheet);
|
||||||
|
sheets = sheets.concat(imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
let forms = sheets.map((sheet) => {
|
||||||
|
let actor = this._createStyleSheetActor(sheet);
|
||||||
|
return actor.form();
|
||||||
|
});
|
||||||
|
|
||||||
|
return forms;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the stylesheets @imported from a stylesheet.
|
||||||
|
*
|
||||||
|
* @param {DOMStyleSheet} styleSheet
|
||||||
|
* Style sheet to search
|
||||||
|
* @return {array}
|
||||||
|
* All the imported stylesheets
|
||||||
|
*/
|
||||||
|
_getImported: function(styleSheet) {
|
||||||
|
let imported = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < styleSheet.cssRules.length; i++) {
|
||||||
|
let rule = styleSheet.cssRules[i];
|
||||||
|
if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
|
||||||
|
// Associated styleSheet may be null if it has already been seen due to
|
||||||
|
// duplicate @imports for the same URL.
|
||||||
|
if (!rule.styleSheet) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
imported.push(rule.styleSheet);
|
||||||
|
|
||||||
|
// recurse imports in this stylesheet as well
|
||||||
|
imported = imported.concat(this._getImported(rule.styleSheet));
|
||||||
|
}
|
||||||
|
else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||||
|
// @import rules must precede all others except @charset
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imported;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new actor for a style sheet, if it hasn't
|
||||||
|
* already been created, and return it.
|
||||||
|
*
|
||||||
|
* @param {DOMStyleSheet} aStyleSheet
|
||||||
|
* The style sheet to create an actor for.
|
||||||
|
* @return {StyleSheetActor}
|
||||||
|
* The actor for this style sheet
|
||||||
|
*/
|
||||||
|
_createStyleSheetActor: function(aStyleSheet)
|
||||||
|
{
|
||||||
|
if (this._sheets.has(aStyleSheet)) {
|
||||||
|
return this._sheets.get(aStyleSheet);
|
||||||
|
}
|
||||||
|
let actor = new StyleSheetActor(aStyleSheet, this);
|
||||||
|
this._actorPool.addActor(actor);
|
||||||
|
this._sheets.set(aStyleSheet, actor);
|
||||||
|
return actor;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all the current stylesheet actors in map.
|
||||||
|
*/
|
||||||
|
_clearStyleSheetActors: function() {
|
||||||
|
for (let actor in this._sheets) {
|
||||||
|
this.releaseActor(this._sheets[actor]);
|
||||||
|
}
|
||||||
|
this._sheets.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actors of all the stylesheets in the current document.
|
||||||
|
*
|
||||||
|
* @return {object} JSON message with the stylesheet actors' forms
|
||||||
|
*/
|
||||||
|
onGetStyleSheets: function() {
|
||||||
|
let forms = this._addStyleSheets(this.doc.styleSheets);
|
||||||
|
return { "styleSheets": forms };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for style sheet loading event. Add
|
||||||
|
* a new actor for the sheet and notify.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
_onSheetLoaded: function(event) {
|
||||||
|
let style = event.target;
|
||||||
|
style.removeEventListener("load", this._onSheetLoaded, false);
|
||||||
|
|
||||||
|
let actor = this._createStyleSheetActor(style.sheet);
|
||||||
|
this._notifyStyleSheetsAdded([actor.form()]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new style sheet in the document with the given text.
|
||||||
|
* Return an actor for it.
|
||||||
|
*
|
||||||
|
* @param {object} request
|
||||||
|
* Debugging protocol request object, with 'text property'
|
||||||
|
* @return {object}
|
||||||
|
* Object with 'styelSheet' property for form on new actor.
|
||||||
|
*/
|
||||||
|
onNewStyleSheet: function(request) {
|
||||||
|
let parent = this.doc.documentElement;
|
||||||
|
let style = this.doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
|
||||||
|
style.setAttribute("type", "text/css");
|
||||||
|
|
||||||
|
if (request.text) {
|
||||||
|
style.appendChild(this.doc.createTextNode(request.text));
|
||||||
|
}
|
||||||
|
parent.appendChild(style);
|
||||||
|
|
||||||
|
let actor = this._createStyleSheetActor(style.sheet);
|
||||||
|
return { styleSheet: actor.form() };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request types this actor can handle.
|
||||||
|
*/
|
||||||
|
StyleEditorActor.prototype.requestTypes = {
|
||||||
|
"getStyleSheets": StyleEditorActor.prototype.onGetStyleSheets,
|
||||||
|
"newStyleSheet": StyleEditorActor.prototype.onNewStyleSheet,
|
||||||
|
"getBaseURI": StyleEditorActor.prototype.onGetBaseURI,
|
||||||
|
"newDocument": StyleEditorActor.prototype.onNewDocument
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function StyleSheetActor(aStyleSheet, aParentActor) {
|
||||||
|
this.styleSheet = aStyleSheet;
|
||||||
|
this.parentActor = aParentActor;
|
||||||
|
|
||||||
|
// text and index are unknown until source load
|
||||||
|
this.text = null;
|
||||||
|
this._styleSheetIndex = -1;
|
||||||
|
|
||||||
|
this._transitionRefCount = 0;
|
||||||
|
|
||||||
|
this._onSourceLoad = this._onSourceLoad.bind(this);
|
||||||
|
this._notifyError = this._notifyError.bind(this);
|
||||||
|
|
||||||
|
// if this sheet has an @import, then it's rules are loaded async
|
||||||
|
let ownerNode = this.styleSheet.ownerNode;
|
||||||
|
if (ownerNode) {
|
||||||
|
let onSheetLoaded = function(event) {
|
||||||
|
ownerNode.removeEventListener("load", onSheetLoaded, false);
|
||||||
|
this._notifyPropertyChanged("ruleCount");
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
ownerNode.addEventListener("load", onSheetLoaded, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleSheetActor.prototype = {
|
||||||
|
actorPrefix: "stylesheet",
|
||||||
|
|
||||||
|
toString: function() {
|
||||||
|
return "[StyleSheetActor " + this.actorID + "]";
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnect: function() {
|
||||||
|
this.parentActor.releaseActor(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Window of target
|
||||||
|
*/
|
||||||
|
get win() {
|
||||||
|
return this.parentActor._window;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document of target.
|
||||||
|
*/
|
||||||
|
get doc() {
|
||||||
|
return this.win.document;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the index (order) of stylesheet in the document.
|
||||||
|
*
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
get styleSheetIndex()
|
||||||
|
{
|
||||||
|
if (this._styleSheetIndex == -1) {
|
||||||
|
for (let i = 0; i < this.doc.styleSheets.length; i++) {
|
||||||
|
if (this.doc.styleSheets[i] == this.styleSheet) {
|
||||||
|
this._styleSheetIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._styleSheetIndex;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current state of the actor
|
||||||
|
*
|
||||||
|
* @return {object}
|
||||||
|
* With properties of the underlying stylesheet, plus 'text',
|
||||||
|
* 'styleSheetIndex' and 'parentActor' if it's @imported
|
||||||
|
*/
|
||||||
|
form: function() {
|
||||||
|
let form = {
|
||||||
|
actor: this.actorID, // actorID is set when this actor is added to a pool
|
||||||
|
href: this.styleSheet.href,
|
||||||
|
disabled: this.styleSheet.disabled,
|
||||||
|
title: this.styleSheet.title,
|
||||||
|
styleSheetIndex: this.styleSheetIndex,
|
||||||
|
text: this.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// get parent actor if this sheet was @imported
|
||||||
|
let parent = this.styleSheet.parentStyleSheet;
|
||||||
|
if (parent) {
|
||||||
|
form.parentActor = this.parentActor._sheets.get(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
form.ruleCount = this.styleSheet.cssRules.length;
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
// stylesheet had an @import rule that wasn't loaded yet
|
||||||
|
}
|
||||||
|
|
||||||
|
return form;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the disabled property of the style sheet
|
||||||
|
*
|
||||||
|
* @return {object}
|
||||||
|
* 'disabled' - the disabled state after toggling.
|
||||||
|
*/
|
||||||
|
onToggleDisabled: function() {
|
||||||
|
this.styleSheet.disabled = !this.styleSheet.disabled;
|
||||||
|
this._notifyPropertyChanged("disabled");
|
||||||
|
|
||||||
|
return { disabled: this.styleSheet.disabled };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event notifying that a property of the stylesheet
|
||||||
|
* has changed.
|
||||||
|
*
|
||||||
|
* @param {string} property
|
||||||
|
* Name of the changed property
|
||||||
|
*/
|
||||||
|
_notifyPropertyChanged: function(property) {
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "propertyChange-" + this.actorID,
|
||||||
|
property: property,
|
||||||
|
value: this.form()[property]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event notifying that an error has occured
|
||||||
|
*
|
||||||
|
* @param {string} message
|
||||||
|
* Error message
|
||||||
|
*/
|
||||||
|
_notifyError: function(message) {
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "error-" + this.actorID,
|
||||||
|
errorMessage: message
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for event when the style sheet's full text has been
|
||||||
|
* loaded from its source.
|
||||||
|
*
|
||||||
|
* @param {string} source
|
||||||
|
* Text of the style sheet
|
||||||
|
* @param {[type]} charset
|
||||||
|
* Optional charset of the source
|
||||||
|
*/
|
||||||
|
_onSourceLoad: function(source, charset) {
|
||||||
|
this.text = this._decodeCSSCharset(source, charset || "");
|
||||||
|
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "sourceLoad-" + this.actorID,
|
||||||
|
source: this.text
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the source of the style sheet from its URL
|
||||||
|
*/
|
||||||
|
onFetchSource: function() {
|
||||||
|
if (!this.styleSheet.href) {
|
||||||
|
// this is an inline <style> sheet
|
||||||
|
let source = this.styleSheet.ownerNode.textContent;
|
||||||
|
this._onSourceLoad(source);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let scheme = Services.io.extractScheme(this.styleSheet.href);
|
||||||
|
switch (scheme) {
|
||||||
|
case "file":
|
||||||
|
this._styleSheetFilePath = this.styleSheet.href;
|
||||||
|
case "chrome":
|
||||||
|
case "resource":
|
||||||
|
this._loadSourceFromFile(this.styleSheet.href);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._loadSourceFromCache(this.styleSheet.href);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a CSS source string to unicode according to the character set rules
|
||||||
|
* defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>.
|
||||||
|
*
|
||||||
|
* @param string string
|
||||||
|
* Source of a CSS stylesheet, loaded from file or cache.
|
||||||
|
* @param string channelCharset
|
||||||
|
* Charset of the source string if set by the HTTP channel.
|
||||||
|
* @return string
|
||||||
|
* The CSS string, in unicode.
|
||||||
|
*/
|
||||||
|
_decodeCSSCharset: function(string, channelCharset)
|
||||||
|
{
|
||||||
|
// StyleSheet's charset can be specified from multiple sources
|
||||||
|
|
||||||
|
if (channelCharset.length > 0) {
|
||||||
|
// step 1 of syndata.html: charset given in HTTP header.
|
||||||
|
return this._convertToUnicode(string, channelCharset);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = this.styleSheet;
|
||||||
|
if (sheet) {
|
||||||
|
// Do we have a @charset rule in the stylesheet?
|
||||||
|
// step 2 of syndata.html (without the BOM check).
|
||||||
|
if (sheet.cssRules) {
|
||||||
|
let rules = sheet.cssRules;
|
||||||
|
if (rules.length
|
||||||
|
&& rules.item(0).type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||||
|
return this._convertToUnicode(string, rules.item(0).encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 3: charset attribute of <link> or <style> element, if it exists
|
||||||
|
if (sheet.ownerNode && sheet.ownerNode.getAttribute) {
|
||||||
|
let linkCharset = sheet.ownerNode.getAttribute("charset");
|
||||||
|
if (linkCharset != null) {
|
||||||
|
return this._convertToUnicode(string, linkCharset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 4 (1 of 2): charset of referring stylesheet.
|
||||||
|
let parentSheet = sheet.parentStyleSheet;
|
||||||
|
if (parentSheet && parentSheet.cssRules &&
|
||||||
|
parentSheet.cssRules[0].type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||||
|
return this._convertToUnicode(string,
|
||||||
|
parentSheet.cssRules[0].encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 4 (2 of 2): charset of referring document.
|
||||||
|
if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
|
||||||
|
return this._convertToUnicode(string,
|
||||||
|
sheet.ownerNode.ownerDocument.characterSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 5: default to utf-8.
|
||||||
|
return this._convertToUnicode(string, "UTF-8");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a given string, encoded in a given character set, to unicode.
|
||||||
|
*
|
||||||
|
* @param string string
|
||||||
|
* A string.
|
||||||
|
* @param string charset
|
||||||
|
* A character set.
|
||||||
|
* @return string
|
||||||
|
* A unicode string.
|
||||||
|
*/
|
||||||
|
_convertToUnicode: function(string, charset) {
|
||||||
|
// Decoding primitives.
|
||||||
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||||
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||||
|
|
||||||
|
try {
|
||||||
|
converter.charset = charset;
|
||||||
|
return converter.ConvertToUnicode(string);
|
||||||
|
} catch(e) {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load source from a file or file-like resource.
|
||||||
|
*
|
||||||
|
* @param string href
|
||||||
|
* URL for the stylesheet.
|
||||||
|
*/
|
||||||
|
_loadSourceFromFile: function(href)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
NetUtil.asyncFetch(href, (stream, status) => {
|
||||||
|
if (!Components.isSuccessCode(status)) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source = NetUtil.readInputStreamToString(stream, stream.available());
|
||||||
|
stream.close();
|
||||||
|
this._onSourceLoad(source);
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load source from the HTTP cache.
|
||||||
|
*
|
||||||
|
* @param string href
|
||||||
|
* URL for the stylesheet.
|
||||||
|
*/
|
||||||
|
_loadSourceFromCache: function(href)
|
||||||
|
{
|
||||||
|
let channel = Services.io.newChannel(href, null, null);
|
||||||
|
let chunks = [];
|
||||||
|
let channelCharset = "";
|
||||||
|
let streamListener = { // nsIStreamListener inherits nsIRequestObserver
|
||||||
|
onStartRequest: (aRequest, aContext, aStatusCode) => {
|
||||||
|
if (!Components.isSuccessCode(aStatusCode)) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDataAvailable: (aRequest, aContext, aStream, aOffset, aCount) => {
|
||||||
|
let channel = aRequest.QueryInterface(Ci.nsIChannel);
|
||||||
|
if (!channelCharset) {
|
||||||
|
channelCharset = channel.contentCharset;
|
||||||
|
}
|
||||||
|
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
||||||
|
},
|
||||||
|
onStopRequest: (aRequest, aContext, aStatusCode) => {
|
||||||
|
if (!Components.isSuccessCode(aStatusCode)) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source = chunks.join("");
|
||||||
|
this._onSourceLoad(source, channelCharset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
|
||||||
|
let loadContext = this.win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIWebNavigation)
|
||||||
|
.QueryInterface(Ci.nsILoadContext);
|
||||||
|
channel.setPrivate(loadContext.usePrivateBrowsing);
|
||||||
|
}
|
||||||
|
channel.loadFlags = channel.LOAD_FROM_CACHE;
|
||||||
|
channel.asyncOpen(streamListener, null);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the style sheet in place with new text
|
||||||
|
*
|
||||||
|
* @param {object} request
|
||||||
|
* 'text' - new text
|
||||||
|
* 'transition' - whether to do CSS transition for change.
|
||||||
|
*/
|
||||||
|
onUpdate: function(request) {
|
||||||
|
DOMUtils.parseStyleSheet(this.styleSheet, request.text);
|
||||||
|
|
||||||
|
this._notifyPropertyChanged("ruleCount");
|
||||||
|
|
||||||
|
if (request.transition) {
|
||||||
|
this._insertTransistionRule();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._notifyStyleApplied();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a catch-all transition rule into the document. Set a timeout
|
||||||
|
* to remove the rule after a certain time.
|
||||||
|
*/
|
||||||
|
_insertTransistionRule: function() {
|
||||||
|
// Insert the global transition rule
|
||||||
|
// Use a ref count to make sure we do not add it multiple times.. and remove
|
||||||
|
// it only when all pending StyleEditor-generated transitions ended.
|
||||||
|
if (this._transitionRefCount == 0) {
|
||||||
|
this.styleSheet.insertRule(TRANSITION_RULE, this.styleSheet.cssRules.length);
|
||||||
|
this.doc.documentElement.classList.add(TRANSITION_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._transitionRefCount++;
|
||||||
|
|
||||||
|
// Set up clean up and commit after transition duration (+10% buffer)
|
||||||
|
// @see _onTransitionEnd
|
||||||
|
this.win.setTimeout(this._onTransitionEnd.bind(this),
|
||||||
|
Math.floor(TRANSITION_DURATION_MS * 1.1));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This cleans up class and rule added for transition effect and then
|
||||||
|
* notifies that the style has been applied.
|
||||||
|
*/
|
||||||
|
_onTransitionEnd: function()
|
||||||
|
{
|
||||||
|
if (--this._transitionRefCount == 0) {
|
||||||
|
this.doc.documentElement.classList.remove(TRANSITION_CLASS);
|
||||||
|
this.styleSheet.deleteRule(this.styleSheet.cssRules.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._notifyStyleApplied();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send and event notifying that the new style has been applied fully.
|
||||||
|
*/
|
||||||
|
_notifyStyleApplied: function()
|
||||||
|
{
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "styleApplied-" + this.actorID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleSheetActor.prototype.requestTypes = {
|
||||||
|
"toggleDisabled": StyleSheetActor.prototype.onToggleDisabled,
|
||||||
|
"fetchSource": StyleSheetActor.prototype.onFetchSource,
|
||||||
|
"update": StyleSheetActor.prototype.onUpdate
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerServer.addTabActor(StyleEditorActor, "styleEditorActor");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
|
||||||
|
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||||
|
});
|
|
@ -0,0 +1,397 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let Cu = Components.utils;
|
||||||
|
let Cc = Components.classes;
|
||||||
|
let Ci = Components.interfaces;
|
||||||
|
|
||||||
|
function debug(aMsg) {
|
||||||
|
/*
|
||||||
|
Cc["@mozilla.org/consoleservice;1"]
|
||||||
|
.getService(Ci.nsIConsoleService)
|
||||||
|
.logStringMessage("--*-- WebappsActor : " + aMsg);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a WebappsActor. WebappsActor provides remote access to
|
||||||
|
* install apps.
|
||||||
|
*/
|
||||||
|
function WebappsActor(aConnection) {
|
||||||
|
debug("init");
|
||||||
|
// Load actor dependencies lazily as this actor require extra environnement
|
||||||
|
// preparation to work (like have a profile setup in xpcshell tests)
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||||
|
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||||
|
Cu.import('resource://gre/modules/Services.jsm');
|
||||||
|
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
WebappsActor.prototype = {
|
||||||
|
actorPrefix: "webapps",
|
||||||
|
|
||||||
|
_registerApp: function wa_actorRegisterApp(aApp, aId, aDir) {
|
||||||
|
debug("registerApp");
|
||||||
|
let reg = DOMApplicationRegistry;
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
aApp.installTime = Date.now();
|
||||||
|
aApp.installState = "installed";
|
||||||
|
aApp.removable = true;
|
||||||
|
aApp.id = aId;
|
||||||
|
aApp.basePath = reg.getWebAppsBasePath();
|
||||||
|
aApp.localId = (aId in reg.webapps) ? reg.webapps[aId].localId
|
||||||
|
: reg._nextLocalId();
|
||||||
|
|
||||||
|
reg.webapps[aId] = aApp;
|
||||||
|
reg.updatePermissionsForApp(aId);
|
||||||
|
|
||||||
|
reg._readManifests([{ id: aId }], function(aResult) {
|
||||||
|
let manifest = aResult[0].manifest;
|
||||||
|
aApp.name = manifest.name;
|
||||||
|
if ("_registerSystemMessages" in reg) {
|
||||||
|
reg._registerSystemMessages(manifest, aApp);
|
||||||
|
}
|
||||||
|
if ("_registerActivities" in reg) {
|
||||||
|
reg._registerActivities(manifest, aApp, true);
|
||||||
|
}
|
||||||
|
reg._saveApps(function() {
|
||||||
|
aApp.manifest = manifest;
|
||||||
|
reg.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp });
|
||||||
|
reg.broadcastMessage("Webapps:Install:Return:OK",
|
||||||
|
{ app: aApp,
|
||||||
|
oid: "foo",
|
||||||
|
requestID: "bar"
|
||||||
|
});
|
||||||
|
delete aApp.manifest;
|
||||||
|
self.conn.send({ from: self.actorID,
|
||||||
|
type: "webappsEvent",
|
||||||
|
appId: aId
|
||||||
|
});
|
||||||
|
|
||||||
|
// We can't have appcache for packaged apps.
|
||||||
|
if (!aApp.origin.startsWith("app://")) {
|
||||||
|
reg.startOfflineCacheDownload(new ManifestHelper(manifest, aApp.origin));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Cleanup by removing the temporary directory.
|
||||||
|
aDir.remove(true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_sendError: function wa_actorSendError(aMsg, aId) {
|
||||||
|
debug("Sending error: " + aMsg);
|
||||||
|
this.conn.send(
|
||||||
|
{ from: this.actorID,
|
||||||
|
type: "webappsEvent",
|
||||||
|
appId: aId,
|
||||||
|
error: "installationFailed",
|
||||||
|
message: aMsg
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getAppType: function wa_actorGetAppType(aType) {
|
||||||
|
let type = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
|
||||||
|
|
||||||
|
if (aType) {
|
||||||
|
type = aType == "privileged" ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
|
||||||
|
: aType == "certified" ? Ci.nsIPrincipal.APP_STATUS_CERTIFIED
|
||||||
|
: Ci.nsIPrincipal.APP_STATUS_INSTALLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
|
||||||
|
installHostedApp: function wa_actorInstallHosted(aDir, aId, aReceipts) {
|
||||||
|
debug("installHostedApp");
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
let runnable = {
|
||||||
|
run: function run() {
|
||||||
|
try {
|
||||||
|
// Move manifest.webapp to the destination directory.
|
||||||
|
let manFile = aDir.clone();
|
||||||
|
manFile.append("manifest.webapp");
|
||||||
|
DOMApplicationRegistry._loadJSONAsync(manFile, function(aManifest) {
|
||||||
|
if (!aManifest) {
|
||||||
|
self._sendError("Error Parsing manifest.webapp", aId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let appType = self._getAppType(aManifest.type);
|
||||||
|
|
||||||
|
// In production builds, don't allow installation of certified apps.
|
||||||
|
if (!DOMApplicationRegistry.allowSideloadingCertified &&
|
||||||
|
appType == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
||||||
|
self._sendError("Installing certified apps is not allowed.", aId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The destination directory for this app.
|
||||||
|
let installDir = DOMApplicationRegistry._getAppDir(aId);
|
||||||
|
manFile.moveTo(installDir, "manifest.webapp");
|
||||||
|
|
||||||
|
// Read the origin and manifest url from metadata.json
|
||||||
|
let metaFile = aDir.clone();
|
||||||
|
metaFile.append("metadata.json");
|
||||||
|
DOMApplicationRegistry._loadJSONAsync(metaFile, function(aMetadata) {
|
||||||
|
if (!aMetadata) {
|
||||||
|
self._sendError("Error Parsing metadata.json", aId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aMetadata.origin) {
|
||||||
|
self._sendError("Missing 'origin' property in metadata.json", aId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let origin = aMetadata.origin;
|
||||||
|
let manifestURL = aMetadata.manifestURL ||
|
||||||
|
origin + "/manifest.webapp";
|
||||||
|
// Create a fake app object with the minimum set of properties we need.
|
||||||
|
let app = {
|
||||||
|
origin: origin,
|
||||||
|
installOrigin: aMetadata.installOrigin || origin,
|
||||||
|
manifestURL: manifestURL,
|
||||||
|
appStatus: appType,
|
||||||
|
receipts: aReceipts,
|
||||||
|
};
|
||||||
|
|
||||||
|
self._registerApp(app, aId, aDir);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
// If anything goes wrong, just send it back.
|
||||||
|
self._sendError(e.toString(), aId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.tm.currentThread.dispatch(runnable,
|
||||||
|
Ci.nsIThread.DISPATCH_NORMAL);
|
||||||
|
},
|
||||||
|
|
||||||
|
installPackagedApp: function wa_actorInstallPackaged(aDir, aId, aReceipts) {
|
||||||
|
debug("installPackagedApp");
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
let runnable = {
|
||||||
|
run: function run() {
|
||||||
|
try {
|
||||||
|
// The destination directory for this app.
|
||||||
|
let installDir = DOMApplicationRegistry._getAppDir(aId);
|
||||||
|
|
||||||
|
// Move application.zip to the destination directory, and
|
||||||
|
// extract manifest.webapp there.
|
||||||
|
let zipFile = aDir.clone();
|
||||||
|
zipFile.append("application.zip");
|
||||||
|
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
|
||||||
|
.createInstance(Ci.nsIZipReader);
|
||||||
|
zipReader.open(zipFile);
|
||||||
|
let manFile = installDir.clone();
|
||||||
|
manFile.append("manifest.webapp");
|
||||||
|
zipReader.extract("manifest.webapp", manFile);
|
||||||
|
zipReader.close();
|
||||||
|
zipFile.moveTo(installDir, "application.zip");
|
||||||
|
|
||||||
|
DOMApplicationRegistry._loadJSONAsync(manFile, function(aManifest) {
|
||||||
|
if (!aManifest) {
|
||||||
|
self._sendError("Error Parsing manifest.webapp", aId);
|
||||||
|
}
|
||||||
|
|
||||||
|
let appType = self._getAppType(aManifest.type);
|
||||||
|
|
||||||
|
// In production builds, don't allow installation of certified apps.
|
||||||
|
if (!DOMApplicationRegistry.allowSideloadingCertified &&
|
||||||
|
appType == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
||||||
|
self._sendError("Installing certified apps is not allowed.", aId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let origin = "app://" + aId;
|
||||||
|
|
||||||
|
// Create a fake app object with the minimum set of properties we need.
|
||||||
|
let app = {
|
||||||
|
origin: origin,
|
||||||
|
installOrigin: origin,
|
||||||
|
manifestURL: origin + "/manifest.webapp",
|
||||||
|
appStatus: appType,
|
||||||
|
receipts: aReceipts,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._registerApp(app, aId, aDir);
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
// If anything goes wrong, just send it back.
|
||||||
|
self._sendError(e.toString(), aId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.tm.currentThread.dispatch(runnable,
|
||||||
|
Ci.nsIThread.DISPATCH_NORMAL);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appId : The id of the app we want to install. We will look for
|
||||||
|
* the files for the app in $TMP/b2g/$appId :
|
||||||
|
* For packaged apps: application.zip
|
||||||
|
* For hosted apps: metadata.json and manifest.webapp
|
||||||
|
*/
|
||||||
|
install: function wa_actorInstall(aRequest) {
|
||||||
|
debug("install");
|
||||||
|
|
||||||
|
let appId = aRequest.appId;
|
||||||
|
if (!appId) {
|
||||||
|
return { error: "missingParameter",
|
||||||
|
message: "missing parameter appId" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we are not overriding a preinstalled application.
|
||||||
|
let reg = DOMApplicationRegistry;
|
||||||
|
if (appId in reg.webapps && reg.webapps[appId].removable === false) {
|
||||||
|
return { error: "badParameterType",
|
||||||
|
message: "The application " + appId + " can't be overriden."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let appDir = FileUtils.getDir("TmpD", ["b2g", appId], false, false);
|
||||||
|
|
||||||
|
if (!appDir || !appDir.exists()) {
|
||||||
|
return { error: "badParameterType",
|
||||||
|
message: "missing directory " + appDir.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testFile = appDir.clone();
|
||||||
|
testFile.append("application.zip");
|
||||||
|
|
||||||
|
let receipts = (aRequest.receipts && Array.isArray(aRequest.receipts))
|
||||||
|
? aRequest.receipts
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (testFile.exists()) {
|
||||||
|
this.installPackagedApp(appDir, appId, receipts);
|
||||||
|
} else {
|
||||||
|
let missing =
|
||||||
|
["manifest.webapp", "metadata.json"]
|
||||||
|
.some(function(aName) {
|
||||||
|
testFile = appDir.clone();
|
||||||
|
testFile.append(aName);
|
||||||
|
return !testFile.exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (missing) {
|
||||||
|
try {
|
||||||
|
appDir.remove(true);
|
||||||
|
} catch(e) {}
|
||||||
|
return { error: "badParameterType",
|
||||||
|
message: "hosted app file is missing" }
|
||||||
|
}
|
||||||
|
|
||||||
|
this.installHostedApp(appDir, appId, receipts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { appId: appId, path: appDir.path }
|
||||||
|
},
|
||||||
|
|
||||||
|
getAll: function wa_actorGetAll(aRequest) {
|
||||||
|
debug("getAll");
|
||||||
|
|
||||||
|
let defer = Promise.defer();
|
||||||
|
let reg = DOMApplicationRegistry;
|
||||||
|
reg.getAll(function onsuccess(apps) {
|
||||||
|
defer.resolve({ apps: apps });
|
||||||
|
});
|
||||||
|
|
||||||
|
return defer.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall: function wa_actorUninstall(aRequest) {
|
||||||
|
debug("uninstall");
|
||||||
|
|
||||||
|
let manifestURL = aRequest.manifestURL;
|
||||||
|
if (!manifestURL) {
|
||||||
|
return { error: "missingParameter",
|
||||||
|
message: "missing parameter manifestURL" };
|
||||||
|
}
|
||||||
|
|
||||||
|
let defer = Promise.defer();
|
||||||
|
let reg = DOMApplicationRegistry;
|
||||||
|
reg.uninstall(
|
||||||
|
manifestURL,
|
||||||
|
function onsuccess() {
|
||||||
|
defer.resolve({});
|
||||||
|
},
|
||||||
|
function onfailure(reason) {
|
||||||
|
defer.resolve({ error: reason });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return defer.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
launch: function wa_actorLaunch(aRequest) {
|
||||||
|
debug("launch");
|
||||||
|
|
||||||
|
let manifestURL = aRequest.manifestURL;
|
||||||
|
if (!manifestURL) {
|
||||||
|
return { error: "missingParameter",
|
||||||
|
message: "missing parameter manifestURL" };
|
||||||
|
}
|
||||||
|
|
||||||
|
let defer = Promise.defer();
|
||||||
|
let reg = DOMApplicationRegistry;
|
||||||
|
reg.launch(
|
||||||
|
aRequest.manifestURL,
|
||||||
|
aRequest.startPoint || "",
|
||||||
|
function onsuccess() {
|
||||||
|
defer.resolve({});
|
||||||
|
},
|
||||||
|
function onfailure(reason) {
|
||||||
|
defer.resolve({ error: reason });
|
||||||
|
});
|
||||||
|
|
||||||
|
return defer.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function wa_actorLaunch(aRequest) {
|
||||||
|
debug("close");
|
||||||
|
|
||||||
|
let manifestURL = aRequest.manifestURL;
|
||||||
|
if (!manifestURL) {
|
||||||
|
return { error: "missingParameter",
|
||||||
|
message: "missing parameter manifestURL" };
|
||||||
|
}
|
||||||
|
|
||||||
|
let reg = DOMApplicationRegistry;
|
||||||
|
let app = reg.getAppByManifestURL(manifestURL);
|
||||||
|
if (!app) {
|
||||||
|
return { error: "missingParameter",
|
||||||
|
message: "No application for " + manifestURL };
|
||||||
|
}
|
||||||
|
|
||||||
|
reg.close(app);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request types this actor can handle.
|
||||||
|
*/
|
||||||
|
WebappsActor.prototype.requestTypes = {
|
||||||
|
"install": WebappsActor.prototype.install,
|
||||||
|
"getAll": WebappsActor.prototype.getAll,
|
||||||
|
"launch": WebappsActor.prototype.launch,
|
||||||
|
"close": WebappsActor.prototype.close,
|
||||||
|
"uninstall": WebappsActor.prototype.uninstall
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerServer.addGlobalActor(WebappsActor, "webappsActor");
|
||||||
|
|
|
@ -0,0 +1,866 @@
|
||||||
|
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
/**
|
||||||
|
* Browser-specific actors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yield all windows of type |aWindowType|, from the oldest window to the
|
||||||
|
* youngest, using nsIWindowMediator::getEnumerator. We're usually
|
||||||
|
* interested in "navigator:browser" windows.
|
||||||
|
*/
|
||||||
|
function allAppShellDOMWindows(aWindowType)
|
||||||
|
{
|
||||||
|
let e = windowMediator.getEnumerator(aWindowType);
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
yield e.getNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the window type of the top-level window |aWindow|.
|
||||||
|
*/
|
||||||
|
function appShellDOMWindowType(aWindow) {
|
||||||
|
/* This is what nsIWindowMediator's enumerator checks. */
|
||||||
|
return aWindow.document.documentElement.getAttribute('windowtype');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send Debugger:Shutdown events to all "navigator:browser" windows.
|
||||||
|
*/
|
||||||
|
function sendShutdownEvent() {
|
||||||
|
for (let win of allAppShellDOMWindows("navigator:browser")) {
|
||||||
|
let evt = win.document.createEvent("Event");
|
||||||
|
evt.initEvent("Debugger:Shutdown", true, false);
|
||||||
|
win.document.documentElement.dispatchEvent(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a root actor appropriate for use in a server running in a
|
||||||
|
* browser. The returned root actor:
|
||||||
|
* - respects the factories registered with DebuggerServer.addGlobalActor,
|
||||||
|
* - uses a BrowserTabList to supply tab actors,
|
||||||
|
* - sends all navigator:browser window documents a Debugger:Shutdown event
|
||||||
|
* when it exits.
|
||||||
|
*
|
||||||
|
* * @param aConnection DebuggerServerConnection
|
||||||
|
* The conection to the client.
|
||||||
|
*/
|
||||||
|
function createRootActor(aConnection)
|
||||||
|
{
|
||||||
|
return new RootActor(aConnection,
|
||||||
|
{
|
||||||
|
tabList: new BrowserTabList(aConnection),
|
||||||
|
globalActorFactories: DebuggerServer.globalActorFactories,
|
||||||
|
onShutdown: sendShutdownEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
|
||||||
|
.getService(Ci.nsIWindowMediator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A live list of BrowserTabActors representing the current browser tabs,
|
||||||
|
* to be provided to the root actor to answer 'listTabs' requests.
|
||||||
|
*
|
||||||
|
* This object also takes care of listening for TabClose events and
|
||||||
|
* onCloseWindow notifications, and exiting the BrowserTabActors concerned.
|
||||||
|
*
|
||||||
|
* (See the documentation for RootActor for the definition of the "live
|
||||||
|
* list" interface.)
|
||||||
|
*
|
||||||
|
* @param aConnection DebuggerServerConnection
|
||||||
|
* The connection in which this list's tab actors may participate.
|
||||||
|
*
|
||||||
|
* Some notes:
|
||||||
|
*
|
||||||
|
* This constructor is specific to the desktop browser environment; it
|
||||||
|
* maintains the tab list by tracking XUL windows and their XUL documents'
|
||||||
|
* "tabbrowser", "tab", and "browser" elements. What's entailed in maintaining
|
||||||
|
* an accurate list of open tabs in this context?
|
||||||
|
*
|
||||||
|
* - Opening and closing XUL windows:
|
||||||
|
*
|
||||||
|
* An nsIWindowMediatorListener is notified when new XUL windows (i.e., desktop
|
||||||
|
* windows) are opened and closed. It is not notified of individual content
|
||||||
|
* browser tabs coming and going within such a XUL window. That seems
|
||||||
|
* reasonable enough; it's concerned with XUL windows, not tab elements in the
|
||||||
|
* window's XUL document.
|
||||||
|
*
|
||||||
|
* However, even if we attach TabOpen and TabClose event listeners to each XUL
|
||||||
|
* window as soon as it is created:
|
||||||
|
*
|
||||||
|
* - we do not receive a TabOpen event for the initial empty tab of a new XUL
|
||||||
|
* window; and
|
||||||
|
*
|
||||||
|
* - we do not receive TabClose events for the tabs of a XUL window that has
|
||||||
|
* been closed.
|
||||||
|
*
|
||||||
|
* This means that TabOpen and TabClose events alone are not sufficient to
|
||||||
|
* maintain an accurate list of live tabs and mark tab actors as closed
|
||||||
|
* promptly. Our nsIWindowMediatorListener onCloseWindow handler must find and
|
||||||
|
* exit all actors for tabs that were in the closing window.
|
||||||
|
*
|
||||||
|
* Since this is a bit hairy, we don't make each individual attached tab actor
|
||||||
|
* responsible for noticing when it has been closed; we watch for that, and
|
||||||
|
* promise to call each actor's 'exit' method when it's closed, regardless of
|
||||||
|
* how we learn the news.
|
||||||
|
*
|
||||||
|
* - nsIWindowMediator locks
|
||||||
|
*
|
||||||
|
* nsIWindowMediator holds a lock protecting its list of top-level windows
|
||||||
|
* while it calls nsIWindowMediatorListener methods. nsIWindowMediator's
|
||||||
|
* GetEnumerator method also tries to acquire that lock. Thus, enumerating
|
||||||
|
* windows from within a listener method deadlocks (bug 873589). Rah. One
|
||||||
|
* can sometimes work around this by leaving the enumeration for a later
|
||||||
|
* tick.
|
||||||
|
*
|
||||||
|
* - Dragging tabs between windows:
|
||||||
|
*
|
||||||
|
* When a tab is dragged from one desktop window to another, we receive a
|
||||||
|
* TabOpen event for the new tab, and a TabClose event for the old tab; tab XUL
|
||||||
|
* elements do not really move from one document to the other (although their
|
||||||
|
* linked browser's content window objects do).
|
||||||
|
*
|
||||||
|
* However, while we could thus assume that each tab stays with the XUL window
|
||||||
|
* it belonged to when it was created, I'm not sure this is behavior one should
|
||||||
|
* rely upon. When a XUL window is closed, we take the less efficient, more
|
||||||
|
* conservative approach of simply searching the entire table for actors that
|
||||||
|
* belong to the closing XUL window, rather than trying to somehow track which
|
||||||
|
* XUL window each tab belongs to.
|
||||||
|
*/
|
||||||
|
function BrowserTabList(aConnection)
|
||||||
|
{
|
||||||
|
this._connection = aConnection;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The XUL document of a tabbed browser window has "tab" elements, whose
|
||||||
|
* 'linkedBrowser' JavaScript properties are "browser" elements; those
|
||||||
|
* browsers' 'contentWindow' properties are wrappers on the tabs' content
|
||||||
|
* window objects.
|
||||||
|
*
|
||||||
|
* This map's keys are "browser" XUL elements; it maps each browser element
|
||||||
|
* to the tab actor we've created for its content window, if we've created
|
||||||
|
* one. This map serves several roles:
|
||||||
|
*
|
||||||
|
* - During iteration, we use it to find actors we've created previously.
|
||||||
|
*
|
||||||
|
* - On a TabClose event, we use it to find the tab's actor and exit it.
|
||||||
|
*
|
||||||
|
* - When the onCloseWindow handler is called, we iterate over it to find all
|
||||||
|
* tabs belonging to the closing XUL window, and exit them.
|
||||||
|
*
|
||||||
|
* - When it's empty, and the onListChanged hook is null, we know we can
|
||||||
|
* stop listening for events and notifications.
|
||||||
|
*
|
||||||
|
* We listen for TabClose events and onCloseWindow notifications in order to
|
||||||
|
* send onListChanged notifications, but also to tell actors when their
|
||||||
|
* referent has gone away and remove entries for dead browsers from this map.
|
||||||
|
* If that code is working properly, neither this map nor the actors in it
|
||||||
|
* should ever hold dead tabs alive.
|
||||||
|
*/
|
||||||
|
this._actorByBrowser = new Map();
|
||||||
|
|
||||||
|
/* The current onListChanged handler, or null. */
|
||||||
|
this._onListChanged = null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* True if we've been iterated over since we last called our onListChanged
|
||||||
|
* hook.
|
||||||
|
*/
|
||||||
|
this._mustNotify = false;
|
||||||
|
|
||||||
|
/* True if we're testing, and should throw if consistency checks fail. */
|
||||||
|
this._testing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserTabList.prototype.constructor = BrowserTabList;
|
||||||
|
|
||||||
|
BrowserTabList.prototype.iterator = function() {
|
||||||
|
let topXULWindow = windowMediator.getMostRecentWindow("navigator:browser");
|
||||||
|
|
||||||
|
// As a sanity check, make sure all the actors presently in our map get
|
||||||
|
// picked up when we iterate over all windows' tabs.
|
||||||
|
let initialMapSize = this._actorByBrowser.size;
|
||||||
|
let foundCount = 0;
|
||||||
|
|
||||||
|
// To avoid mysterious behavior if tabs are closed or opened mid-iteration,
|
||||||
|
// we update the map first, and then make a second pass over it to yield
|
||||||
|
// the actors. Thus, the sequence yielded is always a snapshot of the
|
||||||
|
// actors that were live when we began the iteration.
|
||||||
|
|
||||||
|
// Iterate over all navigator:browser XUL windows.
|
||||||
|
for (let win of allAppShellDOMWindows("navigator:browser")) {
|
||||||
|
let selectedTab = win.gBrowser.selectedBrowser;
|
||||||
|
|
||||||
|
// For each tab in this XUL window, ensure that we have an actor for
|
||||||
|
// it, reusing existing actors where possible. We actually iterate
|
||||||
|
// over 'browser' XUL elements, and BrowserTabActor uses
|
||||||
|
// browser.contentWindow.wrappedJSObject as the debuggee global.
|
||||||
|
for (let browser of win.gBrowser.browsers) {
|
||||||
|
// Do we have an existing actor for this browser? If not, create one.
|
||||||
|
let actor = this._actorByBrowser.get(browser);
|
||||||
|
if (actor) {
|
||||||
|
foundCount++;
|
||||||
|
} else {
|
||||||
|
actor = new BrowserTabActor(this._connection, browser, win.gBrowser);
|
||||||
|
this._actorByBrowser.set(browser, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the 'selected' properties on all actors correctly.
|
||||||
|
actor.selected = (win === topXULWindow && browser === selectedTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._testing && initialMapSize !== foundCount)
|
||||||
|
throw Error("_actorByBrowser map contained actors for dead tabs");
|
||||||
|
|
||||||
|
this._mustNotify = true;
|
||||||
|
this._checkListening();
|
||||||
|
|
||||||
|
/* Yield the values. */
|
||||||
|
for (let [browser, actor] of this._actorByBrowser) {
|
||||||
|
yield actor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(BrowserTabList.prototype, 'onListChanged', {
|
||||||
|
enumerable: true, configurable:true,
|
||||||
|
get: function() { return this._onListChanged; },
|
||||||
|
set: function(v) {
|
||||||
|
if (v !== null && typeof v !== 'function') {
|
||||||
|
throw Error("onListChanged property may only be set to 'null' or a function");
|
||||||
|
}
|
||||||
|
this._onListChanged = v;
|
||||||
|
this._checkListening();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of tabs has changed somehow. Call our onListChanged handler, if
|
||||||
|
* one is set, and if we haven't already called it since the last iteration.
|
||||||
|
*/
|
||||||
|
BrowserTabList.prototype._notifyListChanged = function() {
|
||||||
|
if (!this._onListChanged)
|
||||||
|
return;
|
||||||
|
if (this._mustNotify) {
|
||||||
|
this._onListChanged();
|
||||||
|
this._mustNotify = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exit |aActor|, belonging to |aBrowser|, and notify the onListChanged
|
||||||
|
* handle if needed.
|
||||||
|
*/
|
||||||
|
BrowserTabList.prototype._handleActorClose = function(aActor, aBrowser) {
|
||||||
|
if (this._testing) {
|
||||||
|
if (this._actorByBrowser.get(aBrowser) !== aActor) {
|
||||||
|
throw Error("BrowserTabActor not stored in map under given browser");
|
||||||
|
}
|
||||||
|
if (aActor.browser !== aBrowser) {
|
||||||
|
throw Error("actor's browser and map key don't match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._actorByBrowser.delete(aBrowser);
|
||||||
|
aActor.exit();
|
||||||
|
|
||||||
|
this._notifyListChanged();
|
||||||
|
this._checkListening();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure we are listening or not listening for activity elsewhere in
|
||||||
|
* the browser, as appropriate. Other than setting up newly created XUL
|
||||||
|
* windows, all listener / observer connection and disconnection should
|
||||||
|
* happen here.
|
||||||
|
*/
|
||||||
|
BrowserTabList.prototype._checkListening = function() {
|
||||||
|
/*
|
||||||
|
* If we have an onListChanged handler that we haven't sent an announcement
|
||||||
|
* to since the last iteration, we need to watch for tab creation.
|
||||||
|
*
|
||||||
|
* Oddly, we don't need to watch for 'close' events here. If our actor list
|
||||||
|
* is empty, then either it was empty the last time we iterated, and no
|
||||||
|
* close events are possible, or it was not empty the last time we
|
||||||
|
* iterated, but all the actors have since been closed, and we must have
|
||||||
|
* sent a notification already when they closed.
|
||||||
|
*/
|
||||||
|
this._listenForEventsIf(this._onListChanged && this._mustNotify,
|
||||||
|
"_listeningForTabOpen", ["TabOpen", "TabSelect"]);
|
||||||
|
|
||||||
|
/* If we have live actors, we need to be ready to mark them dead. */
|
||||||
|
this._listenForEventsIf(this._actorByBrowser.size > 0,
|
||||||
|
"_listeningForTabClose", ["TabClose"]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must listen to the window mediator in either case, since that's the
|
||||||
|
* only way to find out about tabs that come and go when top-level windows
|
||||||
|
* are opened and closed.
|
||||||
|
*/
|
||||||
|
this._listenToMediatorIf((this._onListChanged && this._mustNotify) ||
|
||||||
|
(this._actorByBrowser.size > 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add or remove event listeners for all XUL windows.
|
||||||
|
*
|
||||||
|
* @param aShouldListen boolean
|
||||||
|
* True if we should add event handlers; false if we should remove them.
|
||||||
|
* @param aGuard string
|
||||||
|
* The name of a guard property of 'this', indicating whether we're
|
||||||
|
* already listening for those events.
|
||||||
|
* @param aEventNames array of strings
|
||||||
|
* An array of event names.
|
||||||
|
*/
|
||||||
|
BrowserTabList.prototype._listenForEventsIf = function(aShouldListen, aGuard, aEventNames) {
|
||||||
|
if (!aShouldListen !== !this[aGuard]) {
|
||||||
|
let op = aShouldListen ? "addEventListener" : "removeEventListener";
|
||||||
|
for (let win of allAppShellDOMWindows("navigator:browser")) {
|
||||||
|
for (let name of aEventNames) {
|
||||||
|
win[op](name, this, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this[aGuard] = aShouldListen;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement nsIDOMEventListener.
|
||||||
|
*/
|
||||||
|
BrowserTabList.prototype.handleEvent = makeInfallible(function(aEvent) {
|
||||||
|
switch (aEvent.type) {
|
||||||
|
case "TabOpen":
|
||||||
|
case "TabSelect":
|
||||||
|
/* Don't create a new actor; iterate will take care of that. Just notify. */
|
||||||
|
this._notifyListChanged();
|
||||||
|
this._checkListening();
|
||||||
|
break;
|
||||||
|
case "TabClose":
|
||||||
|
let browser = aEvent.target.linkedBrowser;
|
||||||
|
let actor = this._actorByBrowser.get(browser);
|
||||||
|
if (actor) {
|
||||||
|
this._handleActorClose(actor, browser);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, "BrowserTabList.prototype.handleEvent");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If |aShouldListen| is true, ensure we've registered a listener with the
|
||||||
|
* window mediator. Otherwise, ensure we haven't registered a listener.
|
||||||
|
*/
|
||||||
|
BrowserTabList.prototype._listenToMediatorIf = function(aShouldListen) {
|
||||||
|
if (!aShouldListen !== !this._listeningToMediator) {
|
||||||
|
let op = aShouldListen ? "addListener" : "removeListener";
|
||||||
|
windowMediator[op](this);
|
||||||
|
this._listeningToMediator = aShouldListen;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nsIWindowMediatorListener implementation.
|
||||||
|
*
|
||||||
|
* See _onTabClosed for explanation of why we needn't actually tweak any
|
||||||
|
* actors or tables here.
|
||||||
|
*
|
||||||
|
* An nsIWindowMediatorListener's methods get passed all sorts of windows; we
|
||||||
|
* only care about the tab containers. Those have 'getBrowser' methods.
|
||||||
|
*/
|
||||||
|
BrowserTabList.prototype.onWindowTitleChange = () => { };
|
||||||
|
|
||||||
|
BrowserTabList.prototype.onOpenWindow = makeInfallible(function(aWindow) {
|
||||||
|
let handleLoad = makeInfallible(() => {
|
||||||
|
/* We don't want any further load events from this window. */
|
||||||
|
aWindow.removeEventListener("load", handleLoad, false);
|
||||||
|
|
||||||
|
if (appShellDOMWindowType(aWindow) !== "navigator:browser")
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Listen for future tab activity.
|
||||||
|
if (this._listeningForTabOpen) {
|
||||||
|
aWindow.addEventListener("TabOpen", this, false);
|
||||||
|
aWindow.addEventListener("TabSelect", this, false);
|
||||||
|
}
|
||||||
|
if (this._listeningForTabClose) {
|
||||||
|
aWindow.addEventListener("TabClose", this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// As explained above, we will not receive a TabOpen event for this
|
||||||
|
// document's initial tab, so we must notify our client of the new tab
|
||||||
|
// this will have.
|
||||||
|
this._notifyListChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You can hardly do anything at all with a XUL window at this point; it
|
||||||
|
* doesn't even have its document yet. Wait until its document has
|
||||||
|
* loaded, and then see what we've got. This also avoids
|
||||||
|
* nsIWindowMediator enumeration from within listeners (bug 873589).
|
||||||
|
*/
|
||||||
|
aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindow);
|
||||||
|
|
||||||
|
aWindow.addEventListener("load", handleLoad, false);
|
||||||
|
}, "BrowserTabList.prototype.onOpenWindow");
|
||||||
|
|
||||||
|
BrowserTabList.prototype.onCloseWindow = makeInfallible(function(aWindow) {
|
||||||
|
aWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindow);
|
||||||
|
|
||||||
|
if (appShellDOMWindowType(aWindow) !== "navigator:browser")
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* nsIWindowMediator deadlocks if you call its GetEnumerator method from
|
||||||
|
* a nsIWindowMediatorListener's onCloseWindow hook (bug 873589), so
|
||||||
|
* handle the close in a different tick.
|
||||||
|
*/
|
||||||
|
Services.tm.currentThread.dispatch(makeInfallible(() => {
|
||||||
|
/*
|
||||||
|
* Scan the entire map for actors representing tabs that were in this
|
||||||
|
* top-level window, and exit them.
|
||||||
|
*/
|
||||||
|
for (let [browser, actor] of this._actorByBrowser) {
|
||||||
|
/* The browser document of a closed window has no default view. */
|
||||||
|
if (!browser.ownerDocument.defaultView) {
|
||||||
|
this._handleActorClose(actor, browser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0);
|
||||||
|
}, "BrowserTabList.prototype.onCloseWindow");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a tab actor for handling requests to a browser tab, like
|
||||||
|
* attaching and detaching. BrowserTabActor respects the actor factories
|
||||||
|
* registered with DebuggerServer.addTabActor.
|
||||||
|
*
|
||||||
|
* @param aConnection DebuggerServerConnection
|
||||||
|
* The conection to the client.
|
||||||
|
* @param aBrowser browser
|
||||||
|
* The browser instance that contains this tab.
|
||||||
|
* @param aTabBrowser tabbrowser
|
||||||
|
* The tabbrowser that can receive nsIWebProgressListener events.
|
||||||
|
*/
|
||||||
|
function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
|
||||||
|
{
|
||||||
|
this.conn = aConnection;
|
||||||
|
this._browser = aBrowser;
|
||||||
|
this._tabbrowser = aTabBrowser;
|
||||||
|
this._tabActorPool = null;
|
||||||
|
// A map of actor names to actor instances provided by extensions.
|
||||||
|
this._extraActors = {};
|
||||||
|
|
||||||
|
this._onWindowCreated = this.onWindowCreated.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX (bug 710213): BrowserTabActor attach/detach/exit/disconnect is a
|
||||||
|
// *complete* mess, needs to be rethought asap.
|
||||||
|
|
||||||
|
BrowserTabActor.prototype = {
|
||||||
|
get browser() { return this._browser; },
|
||||||
|
|
||||||
|
get exited() { return !this.browser; },
|
||||||
|
get attached() { return !!this._attached; },
|
||||||
|
|
||||||
|
_tabPool: null,
|
||||||
|
get tabActorPool() { return this._tabPool; },
|
||||||
|
|
||||||
|
_contextPool: null,
|
||||||
|
get contextActorPool() { return this._contextPool; },
|
||||||
|
|
||||||
|
_pendingNavigation: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified actor to the default actor pool connection, in order to
|
||||||
|
* keep it alive as long as the server is. This is used by breakpoints in the
|
||||||
|
* thread actor.
|
||||||
|
*
|
||||||
|
* @param actor aActor
|
||||||
|
* The actor object.
|
||||||
|
*/
|
||||||
|
addToParentPool: function BTA_addToParentPool(aActor) {
|
||||||
|
this.conn.addActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified actor from the default actor pool.
|
||||||
|
*
|
||||||
|
* @param BreakpointActor aActor
|
||||||
|
* The actor object.
|
||||||
|
*/
|
||||||
|
removeFromParentPool: function BTA_removeFromParentPool(aActor) {
|
||||||
|
this.conn.removeActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
// A constant prefix that will be used to form the actor ID by the server.
|
||||||
|
actorPrefix: "tab",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the tab title.
|
||||||
|
* @return string
|
||||||
|
* Tab title.
|
||||||
|
*/
|
||||||
|
get title() {
|
||||||
|
let title = this.browser.contentTitle;
|
||||||
|
// If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
|
||||||
|
// tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
|
||||||
|
// as the title.
|
||||||
|
if (!title && this._tabbrowser) {
|
||||||
|
title = this._tabbrowser
|
||||||
|
._getTabForContentWindow(this.contentWindow).label;
|
||||||
|
}
|
||||||
|
return title;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the tab URL.
|
||||||
|
* @return string
|
||||||
|
* Tab URL.
|
||||||
|
*/
|
||||||
|
get url() {
|
||||||
|
return this.browser.currentURI.spec;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the tab content window.
|
||||||
|
* @return nsIDOMWindow
|
||||||
|
* Tab content window.
|
||||||
|
*/
|
||||||
|
get contentWindow() {
|
||||||
|
return this.browser.contentWindow;
|
||||||
|
},
|
||||||
|
|
||||||
|
grip: function BTA_grip() {
|
||||||
|
dbg_assert(!this.exited,
|
||||||
|
"grip() shouldn't be called on exited browser actor.");
|
||||||
|
dbg_assert(this.actorID,
|
||||||
|
"tab should have an actorID.");
|
||||||
|
|
||||||
|
let response = {
|
||||||
|
actor: this.actorID,
|
||||||
|
title: this.title,
|
||||||
|
url: this.url
|
||||||
|
};
|
||||||
|
|
||||||
|
// Walk over tab actors added by extensions and add them to a new ActorPool.
|
||||||
|
let actorPool = new ActorPool(this.conn);
|
||||||
|
this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
|
||||||
|
if (!actorPool.isEmpty()) {
|
||||||
|
this._tabActorPool = actorPool;
|
||||||
|
this.conn.addActorPool(this._tabActorPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._appendExtraActors(response);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the actor is removed from the connection.
|
||||||
|
*/
|
||||||
|
disconnect: function BTA_disconnect() {
|
||||||
|
this._detach();
|
||||||
|
this._extraActors = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the root actor when the underlying tab is closed.
|
||||||
|
*/
|
||||||
|
exit: function BTA_exit() {
|
||||||
|
if (this.exited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.attached) {
|
||||||
|
this._detach();
|
||||||
|
this.conn.send({ from: this.actorID,
|
||||||
|
type: "tabDetached" });
|
||||||
|
}
|
||||||
|
|
||||||
|
this._browser = null;
|
||||||
|
this._tabbrowser = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Support for DebuggerServer.addTabActor. */
|
||||||
|
_createExtraActors: CommonCreateExtraActors,
|
||||||
|
_appendExtraActors: CommonAppendExtraActors,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual work of attching to a tab.
|
||||||
|
*/
|
||||||
|
_attach: function BTA_attach() {
|
||||||
|
if (this._attached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a pool for tab-lifetime actors.
|
||||||
|
dbg_assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached.");
|
||||||
|
this._tabPool = new ActorPool(this.conn);
|
||||||
|
this.conn.addActorPool(this._tabPool);
|
||||||
|
|
||||||
|
// ... and a pool for context-lifetime actors.
|
||||||
|
this._pushContext();
|
||||||
|
|
||||||
|
// Watch for globals being created in this tab.
|
||||||
|
this.browser.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
|
||||||
|
this.browser.addEventListener("pageshow", this._onWindowCreated, true);
|
||||||
|
if (this._tabbrowser) {
|
||||||
|
this._progressListener = new DebuggerProgressListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._attached = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a thread actor and a pool for context-lifetime actors. It then sets
|
||||||
|
* up the content window for debugging.
|
||||||
|
*/
|
||||||
|
_pushContext: function BTA_pushContext() {
|
||||||
|
dbg_assert(!this._contextPool, "Can't push multiple contexts");
|
||||||
|
|
||||||
|
this._contextPool = new ActorPool(this.conn);
|
||||||
|
this.conn.addActorPool(this._contextPool);
|
||||||
|
|
||||||
|
this.threadActor = new ThreadActor(this, this.contentWindow.wrappedJSObject);
|
||||||
|
this._contextPool.addActor(this.threadActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exits the current thread actor and removes the context-lifetime actor pool.
|
||||||
|
* The content window is no longer being debugged after this call.
|
||||||
|
*/
|
||||||
|
_popContext: function BTA_popContext() {
|
||||||
|
dbg_assert(!!this._contextPool, "No context to pop.");
|
||||||
|
|
||||||
|
this.conn.removeActorPool(this._contextPool);
|
||||||
|
this._contextPool = null;
|
||||||
|
this.threadActor.exit();
|
||||||
|
this.threadActor = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the actual work of detaching from a tab.
|
||||||
|
*/
|
||||||
|
_detach: function BTA_detach() {
|
||||||
|
if (!this.attached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._progressListener) {
|
||||||
|
this._progressListener.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.browser.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
|
||||||
|
this.browser.removeEventListener("pageshow", this._onWindowCreated, true);
|
||||||
|
|
||||||
|
this._popContext();
|
||||||
|
|
||||||
|
// Shut down actors that belong to this tab's pool.
|
||||||
|
this.conn.removeActorPool(this._tabPool);
|
||||||
|
this._tabPool = null;
|
||||||
|
if (this._tabActorPool) {
|
||||||
|
this.conn.removeActorPool(this._tabActorPool, true);
|
||||||
|
this._tabActorPool = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._attached = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol Request Handlers
|
||||||
|
|
||||||
|
onAttach: function BTA_onAttach(aRequest) {
|
||||||
|
if (this.exited) {
|
||||||
|
return { type: "exited" };
|
||||||
|
}
|
||||||
|
|
||||||
|
this._attach();
|
||||||
|
|
||||||
|
return { type: "tabAttached", threadActor: this.threadActor.actorID };
|
||||||
|
},
|
||||||
|
|
||||||
|
onDetach: function BTA_onDetach(aRequest) {
|
||||||
|
if (!this.attached) {
|
||||||
|
return { error: "wrongState" };
|
||||||
|
}
|
||||||
|
|
||||||
|
this._detach();
|
||||||
|
|
||||||
|
return { type: "detached" };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare to enter a nested event loop by disabling debuggee events.
|
||||||
|
*/
|
||||||
|
preNest: function BTA_preNest() {
|
||||||
|
if (!this.browser) {
|
||||||
|
// The tab is already closed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let windowUtils = this.contentWindow
|
||||||
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
windowUtils.suppressEventHandling(true);
|
||||||
|
windowUtils.suspendTimeouts();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare to exit a nested event loop by enabling debuggee events.
|
||||||
|
*/
|
||||||
|
postNest: function BTA_postNest(aNestData) {
|
||||||
|
if (!this.browser) {
|
||||||
|
// The tab is already closed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let windowUtils = this.contentWindow
|
||||||
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
windowUtils.resumeTimeouts();
|
||||||
|
windowUtils.suppressEventHandling(false);
|
||||||
|
if (this._pendingNavigation) {
|
||||||
|
this._pendingNavigation.resume();
|
||||||
|
this._pendingNavigation = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle location changes, by sending a tabNavigated notification to the
|
||||||
|
* client.
|
||||||
|
*/
|
||||||
|
onWindowCreated:
|
||||||
|
makeInfallible(function BTA_onWindowCreated(evt) {
|
||||||
|
if (evt.target === this.browser.contentDocument) {
|
||||||
|
// pageshow events for non-persisted pages have already been handled by a
|
||||||
|
// prior DOMWindowCreated event.
|
||||||
|
if (evt.type == "pageshow" && !evt.persisted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._attached) {
|
||||||
|
this.threadActor.clearDebuggees();
|
||||||
|
if (this.threadActor.dbg) {
|
||||||
|
this.threadActor.dbg.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._attached) {
|
||||||
|
this.threadActor.global = evt.target.defaultView.wrappedJSObject;
|
||||||
|
if (this.threadActor.attached) {
|
||||||
|
this.threadActor.findGlobals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "BrowserTabActor.prototype.onWindowCreated"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the window.console object is native or overwritten by script in
|
||||||
|
* the page.
|
||||||
|
*
|
||||||
|
* @param nsIDOMWindow aWindow
|
||||||
|
* The window object you want to check.
|
||||||
|
* @return boolean
|
||||||
|
* True if the window.console object is native, or false otherwise.
|
||||||
|
*/
|
||||||
|
hasNativeConsoleAPI: function BTA_hasNativeConsoleAPI(aWindow) {
|
||||||
|
let isNative = false;
|
||||||
|
try {
|
||||||
|
let console = aWindow.wrappedJSObject.console;
|
||||||
|
isNative = "__mozillaConsole__" in console;
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
return isNative;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request types this actor can handle.
|
||||||
|
*/
|
||||||
|
BrowserTabActor.prototype.requestTypes = {
|
||||||
|
"attach": BrowserTabActor.prototype.onAttach,
|
||||||
|
"detach": BrowserTabActor.prototype.onDetach
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DebuggerProgressListener object is an nsIWebProgressListener which
|
||||||
|
* handles onStateChange events for the inspected browser. If the user tries to
|
||||||
|
* navigate away from a paused page, the listener makes sure that the debuggee
|
||||||
|
* is resumed before the navigation begins.
|
||||||
|
*
|
||||||
|
* @param BrowserTabActor aBrowserTabActor
|
||||||
|
* The tab actor associated with this listener.
|
||||||
|
*/
|
||||||
|
function DebuggerProgressListener(aBrowserTabActor) {
|
||||||
|
this._tabActor = aBrowserTabActor;
|
||||||
|
this._tabActor._tabbrowser.addProgressListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
DebuggerProgressListener.prototype = {
|
||||||
|
onStateChange:
|
||||||
|
makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) {
|
||||||
|
let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START;
|
||||||
|
let isStop = aFlag & Ci.nsIWebProgressListener.STATE_STOP;
|
||||||
|
let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
|
||||||
|
let isNetwork = aFlag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
|
||||||
|
let isRequest = aFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
|
||||||
|
let isWindow = aFlag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
|
||||||
|
|
||||||
|
// Skip non-interesting states.
|
||||||
|
if (!isWindow || !isNetwork ||
|
||||||
|
aProgress.DOMWindow != this._tabActor.contentWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStart && aRequest instanceof Ci.nsIChannel) {
|
||||||
|
// Proceed normally only if the debuggee is not paused.
|
||||||
|
if (this._tabActor.threadActor.state == "paused") {
|
||||||
|
aRequest.suspend();
|
||||||
|
this._tabActor.threadActor.onResume();
|
||||||
|
this._tabActor.threadActor.dbg.enabled = false;
|
||||||
|
this._tabActor._pendingNavigation = aRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tabActor.threadActor.disableAllBreakpoints();
|
||||||
|
this._tabActor.conn.send({
|
||||||
|
from: this._tabActor.actorID,
|
||||||
|
type: "tabNavigated",
|
||||||
|
url: aRequest.URI.spec,
|
||||||
|
nativeConsoleAPI: true,
|
||||||
|
state: "start"
|
||||||
|
});
|
||||||
|
} else if (isStop) {
|
||||||
|
if (this._tabActor.threadActor.state == "running") {
|
||||||
|
this._tabActor.threadActor.dbg.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let window = this._tabActor.contentWindow;
|
||||||
|
this._tabActor.conn.send({
|
||||||
|
from: this._tabActor.actorID,
|
||||||
|
type: "tabNavigated",
|
||||||
|
url: this._tabActor.url,
|
||||||
|
title: this._tabActor.title,
|
||||||
|
nativeConsoleAPI: this._tabActor.hasNativeConsoleAPI(window),
|
||||||
|
state: "stop"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, "DebuggerProgressListener.prototype.onStateChange"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the progress listener instance.
|
||||||
|
*/
|
||||||
|
destroy: function DPL_destroy() {
|
||||||
|
if (this._tabActor._tabbrowser.removeProgressListener) {
|
||||||
|
try {
|
||||||
|
this._tabActor._tabbrowser.removeProgressListener(this);
|
||||||
|
} catch (ex) {
|
||||||
|
// This can throw during browser shutdown.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._tabActor._progressListener = null;
|
||||||
|
this._tabActor = null;
|
||||||
|
}
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,294 @@
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 expandtab */
|
||||||
|
/*jshint undef: true es5: true node: true browser: true devel: true
|
||||||
|
forin: true latedef: false */
|
||||||
|
/*global define: true, Cu: true, __URI__: true */
|
||||||
|
//;(function(id, factory) { // Module boilerplate :(
|
||||||
|
// if (typeof(define) === 'function') { // RequireJS
|
||||||
|
// define(factory);
|
||||||
|
// } else if (typeof(require) === 'function') { // CommonJS
|
||||||
|
// factory.call(this, require, exports, module);
|
||||||
|
// } else if (String(this).indexOf('BackstagePass') >= 0) { // JSM
|
||||||
|
// this[factory.name] = {};
|
||||||
|
// try {
|
||||||
|
// this.console = this['Components'].utils
|
||||||
|
// .import('resource://gre/modules/devtools/Console.jsm', {}).console;
|
||||||
|
// }
|
||||||
|
// catch (ex) {
|
||||||
|
// // Avoid failures on different toolkit configurations.
|
||||||
|
// }
|
||||||
|
// factory(function require(uri) {
|
||||||
|
// var imports = {};
|
||||||
|
// this['Components'].utils.import(uri, imports);
|
||||||
|
// return imports;
|
||||||
|
// }, this[factory.name], { uri: __URI__, id: id });
|
||||||
|
// this.EXPORTED_SYMBOLS = [factory.name];
|
||||||
|
// } else { // Browser or alike
|
||||||
|
// var globals = this;
|
||||||
|
// factory(function require(id) {
|
||||||
|
// return globals[id];
|
||||||
|
// }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
|
||||||
|
// }
|
||||||
|
//}).call(this, 'promise/core', function Promise(require, exports, module) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var exports = exports || {};
|
||||||
|
|
||||||
|
//module.metadata = {
|
||||||
|
// "stability": "unstable"
|
||||||
|
//};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal utility: Wraps given `value` into simplified promise, successfully
|
||||||
|
* fulfilled to a given `value`. Note the result is not a complete promise
|
||||||
|
* implementation, as its method `then` does not returns anything.
|
||||||
|
*/
|
||||||
|
function fulfilled(value) {
|
||||||
|
return { then: function then(fulfill) { fulfill(value); } };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal utility: Wraps given input into simplified promise, pre-rejected
|
||||||
|
* with a given `reason`. Note the result is not a complete promise
|
||||||
|
* implementation, as its method `then` does not returns anything.
|
||||||
|
*/
|
||||||
|
function rejected(reason) {
|
||||||
|
return { then: function then(fulfill, reject) { reject(reason); } };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal utility: Returns `true` if given `value` is a promise. Value is
|
||||||
|
* assumed to be a promise if it implements method `then`.
|
||||||
|
*/
|
||||||
|
function isPromise(value) {
|
||||||
|
return value && typeof(value.then) === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates deferred object containing fresh promise & methods to either resolve
|
||||||
|
* or reject it. The result is an object with the following properties:
|
||||||
|
* - `promise` Eventual value representation implementing CommonJS [Promises/A]
|
||||||
|
* (http://wiki.commonjs.org/wiki/Promises/A) API.
|
||||||
|
* - `resolve` Single shot function that resolves enclosed `promise` with a
|
||||||
|
* given `value`.
|
||||||
|
* - `reject` Single shot function that rejects enclosed `promise` with a given
|
||||||
|
* `reason`.
|
||||||
|
*
|
||||||
|
* An optional `prototype` argument is used as a prototype of the returned
|
||||||
|
* `promise` allowing one to implement additional API. If prototype is not
|
||||||
|
* passed then it falls back to `Object.prototype`.
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* function fetchURI(uri, type) {
|
||||||
|
* var deferred = defer();
|
||||||
|
* var request = new XMLHttpRequest();
|
||||||
|
* request.open("GET", uri, true);
|
||||||
|
* request.responseType = type;
|
||||||
|
* request.onload = function onload() {
|
||||||
|
* deferred.resolve(request.response);
|
||||||
|
* }
|
||||||
|
* request.onerror = function(event) {
|
||||||
|
* deferred.reject(event);
|
||||||
|
* }
|
||||||
|
* request.send();
|
||||||
|
*
|
||||||
|
* return deferred.promise;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function defer(prototype) {
|
||||||
|
// Define FIFO queue of observer pairs. Once promise is resolved & all queued
|
||||||
|
// observers are forwarded to `result` and variable is set to `null`.
|
||||||
|
var observers = [];
|
||||||
|
|
||||||
|
// Promise `result`, which will be assigned a resolution value once promise
|
||||||
|
// is resolved. Note that result will always be assigned promise (or alike)
|
||||||
|
// object to take care of propagation through promise chains. If result is
|
||||||
|
// `null` promise is not resolved yet.
|
||||||
|
var result = null;
|
||||||
|
|
||||||
|
prototype = (prototype || prototype === null) ? prototype : Object.prototype;
|
||||||
|
|
||||||
|
// Create an object implementing promise API.
|
||||||
|
var promise = Object.create(prototype, {
|
||||||
|
then: { value: function then(onFulfill, onError) {
|
||||||
|
var deferred = defer(prototype);
|
||||||
|
|
||||||
|
function resolve(value) {
|
||||||
|
// If `onFulfill` handler is provided resolve `deferred.promise` with
|
||||||
|
// result of invoking it with a resolution value. If handler is not
|
||||||
|
// provided propagate value through.
|
||||||
|
try {
|
||||||
|
deferred.resolve(onFulfill ? onFulfill(value) : value);
|
||||||
|
}
|
||||||
|
// `onFulfill` may throw exception in which case resulting promise
|
||||||
|
// is rejected with thrown exception.
|
||||||
|
catch(error) {
|
||||||
|
if (exports._reportErrors && typeof(console) === 'object')
|
||||||
|
console.error(error);
|
||||||
|
// Note: Following is equivalent of `deferred.reject(error)`,
|
||||||
|
// we use this shortcut to reduce a stack.
|
||||||
|
deferred.resolve(rejected(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reject(reason) {
|
||||||
|
try {
|
||||||
|
if (onError) deferred.resolve(onError(reason));
|
||||||
|
else deferred.resolve(rejected(reason));
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
if (exports._reportErrors && typeof(console) === 'object')
|
||||||
|
console.error(error);
|
||||||
|
deferred.resolve(rejected(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If enclosed promise (`this.promise`) observers queue is still alive
|
||||||
|
// enqueue a new observer pair into it. Note that this does not
|
||||||
|
// necessary means that promise is pending, it may already be resolved,
|
||||||
|
// but we still have to queue observers to guarantee an order of
|
||||||
|
// propagation.
|
||||||
|
if (observers) {
|
||||||
|
observers.push({ resolve: resolve, reject: reject });
|
||||||
|
}
|
||||||
|
// Otherwise just forward observer pair right to a `result` promise.
|
||||||
|
else {
|
||||||
|
result.then(resolve, reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
|
||||||
|
var deferred = {
|
||||||
|
promise: promise,
|
||||||
|
/**
|
||||||
|
* Resolves associated `promise` to a given `value`, unless it's already
|
||||||
|
* resolved or rejected. Note that resolved promise is not necessary a
|
||||||
|
* successfully fulfilled. Promise may be resolved with a promise `value`
|
||||||
|
* in which case `value` promise's fulfillment / rejection will propagate
|
||||||
|
* up to a promise resolved with `value`.
|
||||||
|
*/
|
||||||
|
resolve: function resolve(value) {
|
||||||
|
if (!result) {
|
||||||
|
// Store resolution `value` in a `result` as a promise, so that all
|
||||||
|
// the subsequent handlers can be simply forwarded to it. Since
|
||||||
|
// `result` will be a promise all the value / error propagation will
|
||||||
|
// be uniformly taken care of.
|
||||||
|
result = isPromise(value) ? value : fulfilled(value);
|
||||||
|
|
||||||
|
// Forward already registered observers to a `result` promise in the
|
||||||
|
// order they were registered. Note that we intentionally dequeue
|
||||||
|
// observer at a time until queue is exhausted. This makes sure that
|
||||||
|
// handlers registered as side effect of observer forwarding are
|
||||||
|
// queued instead of being invoked immediately, guaranteeing FIFO
|
||||||
|
// order.
|
||||||
|
while (observers.length) {
|
||||||
|
var observer = observers.shift();
|
||||||
|
result.then(observer.resolve, observer.reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once `observers` queue is exhausted we `null`-ify it, so that
|
||||||
|
// new handlers are forwarded straight to the `result`.
|
||||||
|
observers = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Rejects associated `promise` with a given `reason`, unless it's already
|
||||||
|
* resolved / rejected. This is just a (better performing) convenience
|
||||||
|
* shortcut for `deferred.resolve(reject(reason))`.
|
||||||
|
*/
|
||||||
|
reject: function reject(reason) {
|
||||||
|
// Note that if promise is resolved that does not necessary means that it
|
||||||
|
// is successfully fulfilled. Resolution value may be a promise in which
|
||||||
|
// case its result propagates. In other words if promise `a` is resolved
|
||||||
|
// with promise `b`, `a` is either fulfilled or rejected depending
|
||||||
|
// on weather `b` is fulfilled or rejected. Here `deferred.promise` is
|
||||||
|
// resolved with a promise pre-rejected with a given `reason`, there for
|
||||||
|
// `deferred.promise` is rejected with a given `reason`. This may feel
|
||||||
|
// little awkward first, but doing it this way greatly simplifies
|
||||||
|
// propagation through promise chains.
|
||||||
|
deferred.resolve(rejected(reason));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred;
|
||||||
|
}
|
||||||
|
exports.defer = defer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise resolved to a given `value`. Optionally a second
|
||||||
|
* `prototype` argument may be provided to be used as a prototype for the
|
||||||
|
* returned promise.
|
||||||
|
*/
|
||||||
|
function resolve(value, prototype) {
|
||||||
|
var deferred = defer(prototype);
|
||||||
|
deferred.resolve(value);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
exports.resolve = resolve;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise rejected with a given `reason`. Optionally a second
|
||||||
|
* `prototype` argument may be provided to be used as a prototype for the
|
||||||
|
* returned promise.
|
||||||
|
*/
|
||||||
|
function reject(reason, prototype) {
|
||||||
|
var deferred = defer(prototype);
|
||||||
|
deferred.reject(reason);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
exports.reject = reject;
|
||||||
|
|
||||||
|
var promised = (function() {
|
||||||
|
// Note: Define shortcuts and utility functions here in order to avoid
|
||||||
|
// slower property accesses and unnecessary closure creations on each
|
||||||
|
// call of this popular function.
|
||||||
|
|
||||||
|
var call = Function.call;
|
||||||
|
var concat = Array.prototype.concat;
|
||||||
|
|
||||||
|
// Utility function that does following:
|
||||||
|
// execute([ f, self, args...]) => f.apply(self, args)
|
||||||
|
function execute(args) { return call.apply(call, args) }
|
||||||
|
|
||||||
|
// Utility function that takes promise of `a` array and maybe promise `b`
|
||||||
|
// as arguments and returns promise for `a.concat(b)`.
|
||||||
|
function promisedConcat(promises, unknown) {
|
||||||
|
return promises.then(function(values) {
|
||||||
|
return resolve(unknown).then(function(value) {
|
||||||
|
return values.concat([ value ]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return function promised(f, prototype) {
|
||||||
|
/**
|
||||||
|
Returns a wrapped `f`, which when called returns a promise that resolves to
|
||||||
|
`f(...)` passing all the given arguments to it, which by the way may be
|
||||||
|
promises. Optionally second `prototype` argument may be provided to be used
|
||||||
|
a prototype for a returned promise.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
var promise = promised(Array)(1, promise(2), promise(3))
|
||||||
|
promise.then(console.log) // => [ 1, 2, 3 ]
|
||||||
|
**/
|
||||||
|
|
||||||
|
return function promised() {
|
||||||
|
// create array of [ f, this, args... ]
|
||||||
|
return concat.apply([ f, this ], arguments).
|
||||||
|
// reduce it via `promisedConcat` to get promised array of fulfillments
|
||||||
|
reduce(promisedConcat, resolve([], prototype)).
|
||||||
|
// finally map that to promise of `f.apply(this, args...)`
|
||||||
|
then(execute);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
exports.promised = promised;
|
||||||
|
//
|
||||||
|
var all = promised(Array);
|
||||||
|
exports.all = all;
|
||||||
|
//
|
||||||
|
//});
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
//Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
//Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
|
|
||||||
|
|
||||||
|
function check_except(func)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
func();
|
||||||
|
} catch (e) {
|
||||||
|
// do_check_true(true);
|
||||||
|
log("do_check_true(true)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("Should have thrown an exception: " + func.toString());
|
||||||
|
// do_check_true(false);
|
||||||
|
log("do_check_true(false)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
// Should get an exception if we try to interact with DebuggerServer
|
||||||
|
// before we initialize it...
|
||||||
|
check_except(function() {
|
||||||
|
DebuggerServer.openListener(2929);
|
||||||
|
});
|
||||||
|
check_except(DebuggerServer.closeListener);
|
||||||
|
check_except(DebuggerServer.connectPipe);
|
||||||
|
|
||||||
|
// Allow incoming connections.
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
|
||||||
|
// These should still fail because we haven't added a createRootActor
|
||||||
|
// implementation yet.
|
||||||
|
check_except(function() {
|
||||||
|
DebuggerServer.openListener(2929);
|
||||||
|
});
|
||||||
|
check_except(DebuggerServer.closeListener);
|
||||||
|
check_except(DebuggerServer.connectPipe);
|
||||||
|
|
||||||
|
DebuggerServer.addActors("resource://test/testactors.js");
|
||||||
|
|
||||||
|
// Now they should work.
|
||||||
|
DebuggerServer.openListener(2929);
|
||||||
|
DebuggerServer.closeListener();
|
||||||
|
|
||||||
|
// Make sure we got the test's root actor all set up.
|
||||||
|
let client1 = DebuggerServer.connectPipe();
|
||||||
|
client1.hooks = {
|
||||||
|
onPacket: function(aPacket1) {
|
||||||
|
do_check_eq(aPacket1.from, "root");
|
||||||
|
do_check_eq(aPacket1.applicationType, "xpcshell-tests");
|
||||||
|
|
||||||
|
// Spin up a second connection, make sure it has its own root
|
||||||
|
// actor.
|
||||||
|
let client2 = DebuggerServer.connectPipe();
|
||||||
|
client2.hooks = {
|
||||||
|
onPacket: function(aPacket2) {
|
||||||
|
do_check_eq(aPacket2.from, "root");
|
||||||
|
do_check_neq(aPacket1.testConnectionPrefix,
|
||||||
|
aPacket2.testConnectionPrefix);
|
||||||
|
client2.close();
|
||||||
|
},
|
||||||
|
onClosed: function(aResult) {
|
||||||
|
client1.close();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
client2.ready();
|
||||||
|
},
|
||||||
|
|
||||||
|
onClosed: function(aResult) {
|
||||||
|
do_test_finished();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
client1.ready();
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test();
|
|
@ -0,0 +1,98 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
var gTestGlobals = [];
|
||||||
|
DebuggerServer.addTestGlobal = function(aGlobal) {
|
||||||
|
gTestGlobals.push(aGlobal);
|
||||||
|
};
|
||||||
|
|
||||||
|
// A mock tab list, for use by tests. This simply presents each global in
|
||||||
|
// gTestGlobals as a tab, and the list is fixed: it never calls its
|
||||||
|
// onListChanged handler.
|
||||||
|
//
|
||||||
|
// As implemented now, we consult gTestGlobals when we're constructed, not
|
||||||
|
// when we're iterated over, so tests have to add their globals before the
|
||||||
|
// root actor is created.
|
||||||
|
function TestTabList(aConnection) {
|
||||||
|
this.conn = aConnection;
|
||||||
|
|
||||||
|
// An array of actors for each global added with
|
||||||
|
// DebuggerServer.addTestGlobal.
|
||||||
|
this._tabActors = [];
|
||||||
|
|
||||||
|
// A pool mapping those actors' names to the actors.
|
||||||
|
this._tabActorPool = new ActorPool(aConnection);
|
||||||
|
|
||||||
|
for (let global of gTestGlobals) {
|
||||||
|
let actor = new TestTabActor(aConnection, global);
|
||||||
|
actor.selected = false;
|
||||||
|
this._tabActors.push(actor);
|
||||||
|
this._tabActorPool.addActor(actor);
|
||||||
|
}
|
||||||
|
if (this._tabActors.length > 0) {
|
||||||
|
this._tabActors[0].selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
aConnection.addActorPool(this._tabActorPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestTabList.prototype = {
|
||||||
|
constructor: TestTabList,
|
||||||
|
iterator: function() {
|
||||||
|
for (let actor of this._tabActors) {
|
||||||
|
yield actor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function createRootActor(aConnection)
|
||||||
|
{
|
||||||
|
let root = new RootActor(aConnection,
|
||||||
|
{ tabList: new TestTabList(aConnection) });
|
||||||
|
root.applicationType = "xpcshell-tests";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestTabActor(aConnection, aGlobal)
|
||||||
|
{
|
||||||
|
this.conn = aConnection;
|
||||||
|
this._global = aGlobal;
|
||||||
|
this._threadActor = new ThreadActor(this, this._global);
|
||||||
|
this.conn.addActor(this._threadActor);
|
||||||
|
this._attached = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestTabActor.prototype = {
|
||||||
|
constructor: TestTabActor,
|
||||||
|
actorPrefix: "TestTabActor",
|
||||||
|
|
||||||
|
grip: function() {
|
||||||
|
return { actor: this.actorID, title: this._global.__name };
|
||||||
|
},
|
||||||
|
|
||||||
|
onAttach: function(aRequest) {
|
||||||
|
this._attached = true;
|
||||||
|
return { type: "tabAttached", threadActor: this._threadActor.actorID };
|
||||||
|
},
|
||||||
|
|
||||||
|
onDetach: function(aRequest) {
|
||||||
|
if (!this._attached) {
|
||||||
|
return { "error":"wrongState" };
|
||||||
|
}
|
||||||
|
return { type: "detached" };
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hooks for use by TestTabActors.
|
||||||
|
addToParentPool: function(aActor) {
|
||||||
|
this.conn.addActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeFromParentPool: function(aActor) {
|
||||||
|
this.conn.removeActor(aActor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TestTabActor.prototype.requestTypes = {
|
||||||
|
"attach": TestTabActor.prototype.onAttach,
|
||||||
|
"detach": TestTabActor.prototype.onDetach
|
||||||
|
};
|
|
@ -0,0 +1,921 @@
|
||||||
|
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
/**
|
||||||
|
* Toolkit glue for the remote debugging protocol, loaded into the
|
||||||
|
* debugging global.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//const Ci = Components.interfaces;
|
||||||
|
//const Cc = Components.classes;
|
||||||
|
//const CC = Components.Constructor;
|
||||||
|
//const Cu = Components.utils;
|
||||||
|
//const Cr = Components.results;
|
||||||
|
//const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
|
||||||
|
//
|
||||||
|
//Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
//Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
//let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
|
||||||
|
//
|
||||||
|
//Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||||
|
//addDebuggerToGlobal(this);
|
||||||
|
//
|
||||||
|
//Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||||
|
//const { defer, resolve, reject, all } = Promise;
|
||||||
|
//
|
||||||
|
//Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
|
||||||
|
//
|
||||||
|
//loadSubScript.call(this, "resource://gre/modules/devtools/DevToolsUtils.js");
|
||||||
|
|
||||||
|
let wantLogging = true;
|
||||||
|
let debuggerServer = null;
|
||||||
|
|
||||||
|
function dumpn(str) {
|
||||||
|
if (wantLogging) {
|
||||||
|
log("DBG-SERVER: " + str + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbg_assert(cond, e) {
|
||||||
|
if (!cond) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//loadSubScript.call(this, "resource://gre/modules/devtools/server/transport.js");
|
||||||
|
|
||||||
|
// XPCOM constructors
|
||||||
|
// const ServerSocket = CC("@mozilla.org/network/server-socket;1",
|
||||||
|
// "nsIServerSocket",
|
||||||
|
// "initSpecialConnection");
|
||||||
|
|
||||||
|
function ServerSocket(aPort, flags, c){
|
||||||
|
this.asyncListen = function(server){
|
||||||
|
log("asyncListen....");
|
||||||
|
debuggerServer = server;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var gRegisteredModules = Object.create(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ModuleAPI object is passed to modules loaded using the
|
||||||
|
* DebuggerServer.registerModule() API. Modules can use this
|
||||||
|
* object to register actor factories.
|
||||||
|
* Factories registered through the module API will be removed
|
||||||
|
* when the module is unregistered or when the server is
|
||||||
|
* destroyed.
|
||||||
|
*/
|
||||||
|
function ModuleAPI() {
|
||||||
|
let activeTabActors = new Set();
|
||||||
|
let activeGlobalActors = new Set();
|
||||||
|
|
||||||
|
return {
|
||||||
|
// See DebuggerServer.addGlobalActor for a description.
|
||||||
|
addGlobalActor: function(factory, name) {
|
||||||
|
DebuggerServer.addGlobalActor(factory, name);
|
||||||
|
activeGlobalActors.add(factory);
|
||||||
|
},
|
||||||
|
// See DebuggerServer.removeGlobalActor for a description.
|
||||||
|
removeGlobalActor: function(factory) {
|
||||||
|
DebuggerServer.removeGlobalActor(factory);
|
||||||
|
activeGlobalActors.delete(factory);
|
||||||
|
},
|
||||||
|
|
||||||
|
// See DebuggerServer.addTabActor for a description.
|
||||||
|
addTabActor: function(factory, name) {
|
||||||
|
DebuggerServer.addTabActor(factory, name);
|
||||||
|
activeTabActors.add(factory);
|
||||||
|
},
|
||||||
|
// See DebuggerServer.removeTabActor for a description.
|
||||||
|
removeTabActor: function(factory) {
|
||||||
|
DebuggerServer.removeTabActor(factory);
|
||||||
|
activeTabActors.delete(factory);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Destroy the module API object, unregistering any
|
||||||
|
// factories registered by the module.
|
||||||
|
destroy: function() {
|
||||||
|
for (let factory of activeTabActors) {
|
||||||
|
DebuggerServer.removeTabActor(factory);
|
||||||
|
}
|
||||||
|
activeTabActors = null;
|
||||||
|
for (let factory of activeGlobalActors) {
|
||||||
|
DebuggerServer.removeGlobalActor(factory);
|
||||||
|
}
|
||||||
|
activeGlobalActors = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Public API
|
||||||
|
*/
|
||||||
|
var DebuggerServer = {
|
||||||
|
_listener: null,
|
||||||
|
_initialized: false,
|
||||||
|
_transportInitialized: false,
|
||||||
|
xpcInspector: null,
|
||||||
|
_transport: null,
|
||||||
|
// Number of currently open TCP connections.
|
||||||
|
_socketConnections: 0,
|
||||||
|
// Map of global actor names to actor constructors provided by extensions.
|
||||||
|
globalActorFactories: {},
|
||||||
|
// Map of tab actor names to actor constructors provided by extensions.
|
||||||
|
tabActorFactories: {},
|
||||||
|
|
||||||
|
LONG_STRING_LENGTH: 10000,
|
||||||
|
LONG_STRING_INITIAL_LENGTH: 1000,
|
||||||
|
LONG_STRING_READ_LENGTH: 1000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler function that prompts the user to accept or decline the incoming
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
_allowConnection: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user to accept or decline the incoming connection. This is the
|
||||||
|
* default implementation that products embedding the debugger server may
|
||||||
|
* choose to override.
|
||||||
|
*
|
||||||
|
* @return true if the connection should be permitted, false otherwise
|
||||||
|
*/
|
||||||
|
_defaultAllowConnection: function DS__defaultAllowConnection() {
|
||||||
|
// let title = L10N.getStr("remoteIncomingPromptTitle");
|
||||||
|
// let msg = L10N.getStr("remoteIncomingPromptMessage");
|
||||||
|
// let disableButton = L10N.getStr("remoteIncomingPromptDisable");
|
||||||
|
// let prompt = Services.prompt;
|
||||||
|
// let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_OK +
|
||||||
|
// prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_CANCEL +
|
||||||
|
// prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING +
|
||||||
|
// prompt.BUTTON_POS_1_DEFAULT;
|
||||||
|
// let result = prompt.confirmEx(null, title, msg, flags, null, null,
|
||||||
|
// disableButton, null, { value: false });
|
||||||
|
// if (result == 0) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// if (result == 2) {
|
||||||
|
// DebuggerServer.closeListener(true);
|
||||||
|
// Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the debugger server.
|
||||||
|
*
|
||||||
|
* @param function aAllowConnectionCallback
|
||||||
|
* The embedder-provider callback, that decides whether an incoming
|
||||||
|
* remote protocol conection should be allowed or refused.
|
||||||
|
*/
|
||||||
|
init: function DS_init(aAllowConnectionCallback) {
|
||||||
|
if (this.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xpcInspector = null;//Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
|
||||||
|
this.initTransport(aAllowConnectionCallback);
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/root.js");
|
||||||
|
|
||||||
|
this._initialized = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the debugger server's transport variables. This can be
|
||||||
|
* in place of init() for cases where the jsdebugger isn't needed.
|
||||||
|
*
|
||||||
|
* @param function aAllowConnectionCallback
|
||||||
|
* The embedder-provider callback, that decides whether an incoming
|
||||||
|
* remote protocol conection should be allowed or refused.
|
||||||
|
*/
|
||||||
|
initTransport: function DS_initTransport(aAllowConnectionCallback) {
|
||||||
|
if (this._transportInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._connections = {};
|
||||||
|
this._nextConnID = 0;
|
||||||
|
this._transportInitialized = true;
|
||||||
|
this._allowConnection = aAllowConnectionCallback ?
|
||||||
|
aAllowConnectionCallback :
|
||||||
|
this._defaultAllowConnection;
|
||||||
|
},
|
||||||
|
|
||||||
|
get initialized() this._initialized,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs cleanup tasks before shutting down the debugger server, if no
|
||||||
|
* connections are currently open. Such tasks include clearing any actor
|
||||||
|
* constructors added at runtime. This method should be called whenever a
|
||||||
|
* debugger server is no longer useful, to avoid memory leaks. After this
|
||||||
|
* method returns, the debugger server must be initialized again before use.
|
||||||
|
*/
|
||||||
|
destroy: function DS_destroy() {
|
||||||
|
if (!this._initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let connID of Object.getOwnPropertyNames(this._connections)) {
|
||||||
|
this._connections[connID].close();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let id of Object.getOwnPropertyNames(gRegisteredModules)) {
|
||||||
|
let mod = gRegisteredModules[id];
|
||||||
|
mod.module.unregister(mod.api);
|
||||||
|
}
|
||||||
|
gRegisteredModules = {};
|
||||||
|
|
||||||
|
this.closeListener();
|
||||||
|
this.globalActorFactories = {};
|
||||||
|
this.tabActorFactories = {};
|
||||||
|
delete this._allowConnection;
|
||||||
|
this._transportInitialized = false;
|
||||||
|
this._initialized = false;
|
||||||
|
dumpn("Debugger server is shut down.");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a subscript into the debugging global.
|
||||||
|
*
|
||||||
|
* @param aURL string A url that will be loaded as a subscript into the
|
||||||
|
* debugging global. The user must load at least one script
|
||||||
|
* that implements a createRootActor() function to create the
|
||||||
|
* server's root actor.
|
||||||
|
*/
|
||||||
|
addActors: function DS_addActors(aURL) {
|
||||||
|
//loadSubScript.call(this, aURL);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a CommonJS module with the debugger server.
|
||||||
|
* @param id string
|
||||||
|
* The ID of a CommonJS module. This module must export
|
||||||
|
* 'register' and 'unregister' functions.
|
||||||
|
*/
|
||||||
|
registerModule: function(id) {
|
||||||
|
if (id in gRegisteredModules) {
|
||||||
|
throw new Error("Tried to register a module twice: " + id + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let moduleAPI = ModuleAPI();
|
||||||
|
|
||||||
|
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||||
|
let mod = devtools.require(id);
|
||||||
|
mod.register(moduleAPI);
|
||||||
|
gRegisteredModules[id] = { module: mod, api: moduleAPI };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a module id has been registered.
|
||||||
|
*/
|
||||||
|
isModuleRegistered: function(id) {
|
||||||
|
return (id in gRegisteredModules);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a previously-loaded CommonJS module from the debugger server.
|
||||||
|
*/
|
||||||
|
unregisterModule: function(id) {
|
||||||
|
let mod = gRegisteredModules[id];
|
||||||
|
if (!mod) {
|
||||||
|
throw new Error("Tried to unregister a module that was not previously registered.");
|
||||||
|
}
|
||||||
|
mod.module.unregister(mod.api);
|
||||||
|
mod.api.destroy();
|
||||||
|
delete gRegisteredModules[id];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install Firefox-specific actors.
|
||||||
|
*/
|
||||||
|
addBrowserActors: function DS_addBrowserActors() {
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/script.js");
|
||||||
|
this.addGlobalActor(this.ChromeDebuggerActor, "chromeDebugger");
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
|
||||||
|
if ("nsIProfiler" in Ci)
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
|
||||||
|
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
|
||||||
|
this.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
|
||||||
|
this.registerModule("devtools/server/actors/inspector");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens on the given port for remote debugger connections.
|
||||||
|
*
|
||||||
|
* @param aPort int
|
||||||
|
* The port to listen on.
|
||||||
|
*/
|
||||||
|
openListener: function DS_openListener(aPort) {
|
||||||
|
// if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
this._checkInit();
|
||||||
|
|
||||||
|
// Return early if the server is already listening.
|
||||||
|
if (this._listener) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// let flags = Ci.nsIServerSocket.KeepWhenOffline;
|
||||||
|
// A preference setting can force binding on the loopback interface.
|
||||||
|
// if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
|
||||||
|
// flags |= Ci.nsIServerSocket.LoopbackOnly;
|
||||||
|
// }
|
||||||
|
|
||||||
|
let flags = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let socket = new ServerSocket(aPort, flags, 4);
|
||||||
|
socket.asyncListen(this);
|
||||||
|
this._listener = socket;
|
||||||
|
} catch (e) {
|
||||||
|
dumpn("Could not start debugging listener on port " + aPort + ": " + e);
|
||||||
|
throw "Cr.NS_ERROR_NOT_AVAILABLE";
|
||||||
|
}
|
||||||
|
this._socketConnections++;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a previously-opened TCP listener.
|
||||||
|
*
|
||||||
|
* @param aForce boolean [optional]
|
||||||
|
* If set to true, then the socket will be closed, regardless of the
|
||||||
|
* number of open connections.
|
||||||
|
*/
|
||||||
|
closeListener: function DS_closeListener(aForce) {
|
||||||
|
if (!this._listener || this._socketConnections == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only close the listener when the last connection is closed, or if the
|
||||||
|
// aForce flag is passed.
|
||||||
|
if (--this._socketConnections == 0 || aForce) {
|
||||||
|
this._listener.close();
|
||||||
|
this._listener = null;
|
||||||
|
this._socketConnections = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new connection to the local debugger speaking over a fake
|
||||||
|
* transport. This connection results in straightforward calls to the onPacket
|
||||||
|
* handlers of each side.
|
||||||
|
*
|
||||||
|
* @returns a client-side DebuggerTransport for communicating with
|
||||||
|
* the newly-created connection.
|
||||||
|
*/
|
||||||
|
connectPipe: function DS_connectPipe() {
|
||||||
|
this._checkInit();
|
||||||
|
|
||||||
|
let serverTransport = new LocalDebuggerTransport;
|
||||||
|
let clientTransport = new LocalDebuggerTransport(serverTransport);
|
||||||
|
serverTransport.other = clientTransport;
|
||||||
|
let connection = this._onConnection(serverTransport);
|
||||||
|
|
||||||
|
// I'm putting this here because I trust you.
|
||||||
|
//
|
||||||
|
// There are times, when using a local connection, when you're going
|
||||||
|
// to be tempted to just get direct access to the server. Resist that
|
||||||
|
// temptation! If you succumb to that temptation, you will make the
|
||||||
|
// fine developers that work on Fennec and Firefox OS sad. They're
|
||||||
|
// professionals, they'll try to act like they understand, but deep
|
||||||
|
// down you'll know that you hurt them.
|
||||||
|
//
|
||||||
|
// This reference allows you to give in to that temptation. There are
|
||||||
|
// times this makes sense: tests, for example, and while porting a
|
||||||
|
// previously local-only codebase to the remote protocol.
|
||||||
|
//
|
||||||
|
// But every time you use this, you will feel the shame of having
|
||||||
|
// used a property that starts with a '_'.
|
||||||
|
clientTransport._serverConnection = connection;
|
||||||
|
|
||||||
|
return clientTransport;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// nsIServerSocketListener implementation
|
||||||
|
|
||||||
|
onSocketAccepted:
|
||||||
|
makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) {
|
||||||
|
if (!this._allowConnection()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port);
|
||||||
|
|
||||||
|
let input = aTransport.openInputStream(0, 0, 0);
|
||||||
|
let output = aTransport.openOutputStream(0, 0, 0);
|
||||||
|
let transport = new DebuggerTransport(input, output);
|
||||||
|
DebuggerServer._onConnection(transport);
|
||||||
|
}, "DebuggerServer.onSocketAccepted"),
|
||||||
|
|
||||||
|
onStopListening: function DS_onStopListening(aSocket, status) {
|
||||||
|
dumpn("onStopListening, status: " + status);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises an exception if the server has not been properly initialized.
|
||||||
|
*/
|
||||||
|
_checkInit: function DS_checkInit() {
|
||||||
|
if (!this._transportInitialized) {
|
||||||
|
throw "DebuggerServer has not been initialized.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.createRootActor) {
|
||||||
|
throw "Use DebuggerServer.addActors() to add a root actor implementation.";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new debugger connection for the given transport. Called
|
||||||
|
* after connectPipe() or after an incoming socket connection.
|
||||||
|
*/
|
||||||
|
_onConnection: function DS_onConnection(aTransport) {
|
||||||
|
log("DebuggerServer._onConnection....");
|
||||||
|
|
||||||
|
this._transport = aTransport;
|
||||||
|
|
||||||
|
let connID = "conn" + this._nextConnID++ + '.';
|
||||||
|
let conn = new DebuggerServerConnection(connID, aTransport);
|
||||||
|
this._connections[connID] = conn;
|
||||||
|
|
||||||
|
// Create a root actor for the connection and send the hello packet.
|
||||||
|
conn.rootActor = this.createRootActor(conn);
|
||||||
|
conn.addActor(conn.rootActor);
|
||||||
|
|
||||||
|
aTransport.send(conn.rootActor.sayHello());
|
||||||
|
aTransport.ready();
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the connection from the debugging server.
|
||||||
|
*/
|
||||||
|
_connectionClosed: function DS_connectionClosed(aConnection) {
|
||||||
|
delete this._connections[aConnection.prefix];
|
||||||
|
},
|
||||||
|
|
||||||
|
// DebuggerServer extension API.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers handlers for new tab-scoped request types defined dynamically.
|
||||||
|
* This is used for example by add-ons to augment the functionality of the tab
|
||||||
|
* actor. Note that the name or actorPrefix of the request type is not allowed
|
||||||
|
* to clash with existing protocol packet properties, like 'title', 'url' or
|
||||||
|
* 'actor', since that would break the protocol.
|
||||||
|
*
|
||||||
|
* @param aFunction function
|
||||||
|
* The constructor function for this request type. This expects to be
|
||||||
|
* called as a constructor (i.e. with 'new'), and passed two
|
||||||
|
* arguments: the DebuggerServerConnection, and the BrowserTabActor
|
||||||
|
* with which it will be associated.
|
||||||
|
*
|
||||||
|
* @param aName string [optional]
|
||||||
|
* The name of the new request type. If this is not present, the
|
||||||
|
* actorPrefix property of the constructor prototype is used.
|
||||||
|
*/
|
||||||
|
addTabActor: function DS_addTabActor(aFunction, aName) {
|
||||||
|
let name = aName ? aName : aFunction.prototype.actorPrefix;
|
||||||
|
if (["title", "url", "actor"].indexOf(name) != -1) {
|
||||||
|
throw Error(name + " is not allowed");
|
||||||
|
}
|
||||||
|
if (DebuggerServer.tabActorFactories.hasOwnProperty(name)) {
|
||||||
|
throw Error(name + " already exists");
|
||||||
|
}
|
||||||
|
DebuggerServer.tabActorFactories[name] = aFunction;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters the handler for the specified tab-scoped request type.
|
||||||
|
* This may be used for example by add-ons when shutting down or upgrading.
|
||||||
|
*
|
||||||
|
* @param aFunction function
|
||||||
|
* The constructor function for this request type.
|
||||||
|
*/
|
||||||
|
removeTabActor: function DS_removeTabActor(aFunction) {
|
||||||
|
for (let name in DebuggerServer.tabActorFactories) {
|
||||||
|
let handler = DebuggerServer.tabActorFactories[name];
|
||||||
|
if (handler.name == aFunction.name) {
|
||||||
|
delete DebuggerServer.tabActorFactories[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers handlers for new browser-scoped request types defined
|
||||||
|
* dynamically. This is used for example by add-ons to augment the
|
||||||
|
* functionality of the root actor. Note that the name or actorPrefix of the
|
||||||
|
* request type is not allowed to clash with existing protocol packet
|
||||||
|
* properties, like 'from', 'tabs' or 'selected', since that would break the
|
||||||
|
* protocol.
|
||||||
|
*
|
||||||
|
* @param aFunction function
|
||||||
|
* The constructor function for this request type. This expects to be
|
||||||
|
* called as a constructor (i.e. with 'new'), and passed two
|
||||||
|
* arguments: the DebuggerServerConnection, and the BrowserRootActor
|
||||||
|
* with which it will be associated.
|
||||||
|
*
|
||||||
|
* @param aName string [optional]
|
||||||
|
* The name of the new request type. If this is not present, the
|
||||||
|
* actorPrefix property of the constructor prototype is used.
|
||||||
|
*/
|
||||||
|
addGlobalActor: function DS_addGlobalActor(aFunction, aName) {
|
||||||
|
let name = aName ? aName : aFunction.prototype.actorPrefix;
|
||||||
|
if (["from", "tabs", "selected"].indexOf(name) != -1) {
|
||||||
|
throw Error(name + " is not allowed");
|
||||||
|
}
|
||||||
|
if (DebuggerServer.globalActorFactories.hasOwnProperty(name)) {
|
||||||
|
throw Error(name + " already exists");
|
||||||
|
}
|
||||||
|
DebuggerServer.globalActorFactories[name] = aFunction;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters the handler for the specified browser-scoped request type.
|
||||||
|
* This may be used for example by add-ons when shutting down or upgrading.
|
||||||
|
*
|
||||||
|
* @param aFunction function
|
||||||
|
* The constructor function for this request type.
|
||||||
|
*/
|
||||||
|
removeGlobalActor: function DS_removeGlobalActor(aFunction) {
|
||||||
|
for (let name in DebuggerServer.globalActorFactories) {
|
||||||
|
let handler = DebuggerServer.globalActorFactories[name];
|
||||||
|
if (handler.name == aFunction.name) {
|
||||||
|
delete DebuggerServer.globalActorFactories[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an ActorPool.
|
||||||
|
*
|
||||||
|
* ActorPools are actorID -> actor mapping and storage. These are
|
||||||
|
* used to accumulate and quickly dispose of groups of actors that
|
||||||
|
* share a lifetime.
|
||||||
|
*/
|
||||||
|
function ActorPool(aConnection)
|
||||||
|
{
|
||||||
|
this.conn = aConnection;
|
||||||
|
this._cleanups = {};
|
||||||
|
this._actors = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorPool.prototype = {
|
||||||
|
/**
|
||||||
|
* Add an actor to the actor pool. If the actor doesn't have an ID,
|
||||||
|
* allocate one from the connection.
|
||||||
|
*
|
||||||
|
* @param aActor object
|
||||||
|
* The actor implementation. If the object has a
|
||||||
|
* 'disconnect' property, it will be called when the actor
|
||||||
|
* pool is cleaned up.
|
||||||
|
*/
|
||||||
|
addActor: function AP_addActor(aActor) {
|
||||||
|
aActor.conn = this.conn;
|
||||||
|
if (!aActor.actorID) {
|
||||||
|
let prefix = aActor.actorPrefix;
|
||||||
|
if (typeof aActor == "function") {
|
||||||
|
prefix = aActor.prototype.actorPrefix;
|
||||||
|
}
|
||||||
|
aActor.actorID = this.conn.allocID(prefix || undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aActor.registeredPool) {
|
||||||
|
aActor.registeredPool.removeActor(aActor);
|
||||||
|
}
|
||||||
|
aActor.registeredPool = this;
|
||||||
|
|
||||||
|
this._actors[aActor.actorID] = aActor;
|
||||||
|
if (aActor.disconnect) {
|
||||||
|
this._cleanups[aActor.actorID] = aActor;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function AP_get(aActorID) {
|
||||||
|
return this._actors[aActorID];
|
||||||
|
},
|
||||||
|
|
||||||
|
has: function AP_has(aActorID) {
|
||||||
|
return aActorID in this._actors;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the pool is empty.
|
||||||
|
*/
|
||||||
|
isEmpty: function AP_isEmpty() {
|
||||||
|
return Object.keys(this._actors).length == 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an actor from the actor pool.
|
||||||
|
*/
|
||||||
|
removeActor: function AP_remove(aActor) {
|
||||||
|
delete this._actors[aActor.actorID];
|
||||||
|
delete this._cleanups[aActor.actorID];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match the api expected by the protocol library.
|
||||||
|
*/
|
||||||
|
unmanage: function(aActor) {
|
||||||
|
return this.removeActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run all actor cleanups.
|
||||||
|
*/
|
||||||
|
cleanup: function AP_cleanup() {
|
||||||
|
for each (let actor in this._cleanups) {
|
||||||
|
actor.disconnect();
|
||||||
|
}
|
||||||
|
this._cleanups = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DebuggerServerConnection.
|
||||||
|
*
|
||||||
|
* Represents a connection to this debugging global from a client.
|
||||||
|
* Manages a set of actors and actor pools, allocates actor ids, and
|
||||||
|
* handles incoming requests.
|
||||||
|
*
|
||||||
|
* @param aPrefix string
|
||||||
|
* All actor IDs created by this connection should be prefixed
|
||||||
|
* with aPrefix.
|
||||||
|
* @param aTransport transport
|
||||||
|
* Packet transport for the debugging protocol.
|
||||||
|
*/
|
||||||
|
function DebuggerServerConnection(aPrefix, aTransport)
|
||||||
|
{
|
||||||
|
this._prefix = aPrefix;
|
||||||
|
this._transport = aTransport;
|
||||||
|
this._transport.hooks = this;
|
||||||
|
this._nextID = 1;
|
||||||
|
|
||||||
|
this._actorPool = new ActorPool(this);
|
||||||
|
this._extraPools = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
DebuggerServerConnection.prototype = {
|
||||||
|
_prefix: null,
|
||||||
|
get prefix() { return this._prefix },
|
||||||
|
|
||||||
|
_transport: null,
|
||||||
|
get transport() { return this._transport },
|
||||||
|
|
||||||
|
close: function() {
|
||||||
|
this._transport.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
send: function DSC_send(aPacket) {
|
||||||
|
this.transport.send(aPacket);
|
||||||
|
},
|
||||||
|
|
||||||
|
allocID: function DSC_allocID(aPrefix) {
|
||||||
|
return this.prefix + (aPrefix || '') + this._nextID++;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a map of actor IDs to the connection.
|
||||||
|
*/
|
||||||
|
addActorPool: function DSC_addActorPool(aActorPool) {
|
||||||
|
this._extraPools.push(aActorPool);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a previously-added pool of actors to the connection.
|
||||||
|
*
|
||||||
|
* @param ActorPool aActorPool
|
||||||
|
* The ActorPool instance you want to remove.
|
||||||
|
* @param boolean aCleanup
|
||||||
|
* True if you want to disconnect each actor from the pool, false
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
removeActorPool: function DSC_removeActorPool(aActorPool, aCleanup) {
|
||||||
|
let index = this._extraPools.lastIndexOf(aActorPool);
|
||||||
|
if (index > -1) {
|
||||||
|
let pool = this._extraPools.splice(index, 1);
|
||||||
|
if (aCleanup) {
|
||||||
|
pool.map(function(p) { p.cleanup(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an actor to the default actor pool for this connection.
|
||||||
|
*/
|
||||||
|
addActor: function DSC_addActor(aActor) {
|
||||||
|
this._actorPool.addActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an actor to the default actor pool for this connection.
|
||||||
|
*/
|
||||||
|
removeActor: function DSC_removeActor(aActor) {
|
||||||
|
this._actorPool.removeActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match the api expected by the protocol library.
|
||||||
|
*/
|
||||||
|
unmanage: function(aActor) {
|
||||||
|
return this.removeActor(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up an actor implementation for an actorID. Will search
|
||||||
|
* all the actor pools registered with the connection.
|
||||||
|
*
|
||||||
|
* @param aActorID string
|
||||||
|
* Actor ID to look up.
|
||||||
|
*/
|
||||||
|
getActor: function DSC_getActor(aActorID) {
|
||||||
|
let pool = this.poolFor(aActorID);
|
||||||
|
if (pool) {
|
||||||
|
return pool.get(aActorID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aActorID === "root") {
|
||||||
|
return this.rootActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
poolFor: function DSC_actorPool(aActorID) {
|
||||||
|
if (this._actorPool && this._actorPool.has(aActorID)) {
|
||||||
|
return this._actorPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let pool of this._extraPools) {
|
||||||
|
if (pool.has(aActorID)) {
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_unknownError: function DSC__unknownError(aPrefix, aError) {
|
||||||
|
let errorString = safeErrorString(aError);
|
||||||
|
errorString += "\n" + aError.stack;
|
||||||
|
// Cu.reportError(errorString);
|
||||||
|
dumpn(errorString);
|
||||||
|
return {
|
||||||
|
error: "unknownError",
|
||||||
|
message: (aPrefix + "': " + errorString)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Transport hooks.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by DebuggerTransport to dispatch incoming packets as appropriate.
|
||||||
|
*
|
||||||
|
* @param aPacket object
|
||||||
|
* The incoming packet.
|
||||||
|
*/
|
||||||
|
onPacket: function DSC_onPacket(aPacket) {
|
||||||
|
let actor = this.getActor(aPacket.to);
|
||||||
|
if (!actor) {
|
||||||
|
this.transport.send({ from: aPacket.to ? aPacket.to : "root",
|
||||||
|
error: "noSuchActor" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dyamically-loaded actors have to be created lazily.
|
||||||
|
if (typeof actor == "function") {
|
||||||
|
let instance;
|
||||||
|
try {
|
||||||
|
instance = new actor();
|
||||||
|
} catch (e) {
|
||||||
|
this.transport.send(this._unknownError(
|
||||||
|
"Error occurred while creating actor '" + actor.name,
|
||||||
|
e));
|
||||||
|
}
|
||||||
|
instance.parentID = actor.parentID;
|
||||||
|
// We want the newly-constructed actor to completely replace the factory
|
||||||
|
// actor. Reusing the existing actor ID will make sure ActorPool.addActor
|
||||||
|
// does the right thing.
|
||||||
|
instance.actorID = actor.actorID;
|
||||||
|
actor.registeredPool.addActor(instance);
|
||||||
|
actor = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = null;
|
||||||
|
// log("actor.requestTypes: "+actor.requestTypes+", cb: "+actor.requestTypes[aPacket.type]);
|
||||||
|
// Dispatch the request to the actor.
|
||||||
|
if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
|
||||||
|
try {
|
||||||
|
this.currentPacket = aPacket;
|
||||||
|
ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket, this);
|
||||||
|
} catch(e) {
|
||||||
|
this.transport.send(this._unknownError(
|
||||||
|
"error occurred while processing '" + aPacket.type,
|
||||||
|
e));
|
||||||
|
} finally {
|
||||||
|
delete this.currentPacket;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = { error: "unrecognizedPacketType",
|
||||||
|
message: ('Actor "' + actor.actorID +
|
||||||
|
'" does not recognize the packet type "' +
|
||||||
|
aPacket.type + '"') };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
// This should become an error once we've converted every user
|
||||||
|
// of this to promises in bug 794078.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(ret)
|
||||||
|
.then(null, (e) => {
|
||||||
|
return this._unknownError(
|
||||||
|
"error occurred while processing '" + aPacket.type,
|
||||||
|
e);
|
||||||
|
})
|
||||||
|
.then(function (aResponse) {
|
||||||
|
if (!aResponse.from) {
|
||||||
|
aResponse.from = aPacket.to;
|
||||||
|
}
|
||||||
|
return aResponse;
|
||||||
|
})
|
||||||
|
.then(this.transport.send.bind(this.transport));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by DebuggerTransport when the underlying stream is closed.
|
||||||
|
*
|
||||||
|
* @param aStatus nsresult
|
||||||
|
* The status code that corresponds to the reason for closing
|
||||||
|
* the stream.
|
||||||
|
*/
|
||||||
|
onClosed: function DSC_onClosed(aStatus) {
|
||||||
|
dumpn("Cleaning up connection.");
|
||||||
|
|
||||||
|
this._actorPool.cleanup();
|
||||||
|
this._actorPool = null;
|
||||||
|
this._extraPools.map(function(p) { p.cleanup(); });
|
||||||
|
this._extraPools = null;
|
||||||
|
|
||||||
|
DebuggerServer._connectionClosed(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Debugging helper for inspecting the state of the actor pools.
|
||||||
|
*/
|
||||||
|
_dumpPools: function DSC_dumpPools() {
|
||||||
|
dumpn("/-------------------- dumping pools:");
|
||||||
|
if (this._actorPool) {
|
||||||
|
dumpn("--------------------- actorPool actors: " +
|
||||||
|
uneval(Object.keys(this._actorPool._actors)));
|
||||||
|
}
|
||||||
|
for each (let pool in this._extraPools)
|
||||||
|
dumpn("--------------------- extraPool actors: " +
|
||||||
|
uneval(Object.keys(pool._actors)));
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Debugging helper for inspecting the state of an actor pool.
|
||||||
|
*/
|
||||||
|
_dumpPool: function DSC_dumpPools(aPool) {
|
||||||
|
dumpn("/-------------------- dumping pool:");
|
||||||
|
dumpn("--------------------- actorPool actors: " +
|
||||||
|
uneval(Object.keys(aPool._actors)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Localization convenience methods.
|
||||||
|
*/
|
||||||
|
// let L10N = {
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * L10N shortcut function.
|
||||||
|
// *
|
||||||
|
// * @param string aName
|
||||||
|
// * @return string
|
||||||
|
// */
|
||||||
|
// getStr: function L10N_getStr(aName) {
|
||||||
|
// return this.stringBundle.GetStringFromName(aName);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
|
||||||
|
// return Services.strings.createBundle(DBG_STRINGS_URI);
|
||||||
|
// });
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,31 @@
|
||||||
|
#
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
DEPTH = @DEPTH@
|
||||||
|
topsrcdir = @top_srcdir@
|
||||||
|
srcdir = @srcdir@
|
||||||
|
VPATH = @srcdir@
|
||||||
|
relativesrcdir = @relativesrcdir@
|
||||||
|
|
||||||
|
include $(DEPTH)/config/autoconf.mk
|
||||||
|
|
||||||
|
MOCHITEST_CHROME_FILES = \
|
||||||
|
inspector-helpers.js \
|
||||||
|
inspector-traversal-data.html \
|
||||||
|
test_inspector-changeattrs.html \
|
||||||
|
test_inspector-changevalue.html \
|
||||||
|
test_inspector-mutations-attr.html \
|
||||||
|
test_inspector-mutations-childlist.html \
|
||||||
|
test_inspector-mutations-frameload.html \
|
||||||
|
test_inspector-mutations-value.html \
|
||||||
|
test_inspector-release.html \
|
||||||
|
test_inspector-retain.html \
|
||||||
|
test_inspector-pseudoclass-lock.html \
|
||||||
|
test_inspector-traversal.html \
|
||||||
|
test_unsafeDereference.html \
|
||||||
|
nonchrome_unsafeDereference.html \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
include $(topsrcdir)/config/rules.mk
|
|
@ -0,0 +1,293 @@
|
||||||
|
var Cu = Components.utils;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
// Always log packets when running tests.
|
||||||
|
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||||
|
SimpleTest.registerCleanupFunction(function() {
|
||||||
|
Services.prefs.clearUserPref("devtools.debugger.log");
|
||||||
|
});
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
|
||||||
|
const {_documentWalker} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init(() => true);
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
SimpleTest.registerCleanupFunction(function() {
|
||||||
|
DebuggerServer.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var gAttachCleanups = [];
|
||||||
|
|
||||||
|
SimpleTest.registerCleanupFunction(function() {
|
||||||
|
for (let cleanup of gAttachCleanups) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a tab, load the url, wait for it to signal its readiness,
|
||||||
|
* find the tab with the debugger server, and call the callback.
|
||||||
|
*
|
||||||
|
* Returns a function which can be called to close the opened ta
|
||||||
|
* and disconnect its debugger client.
|
||||||
|
*/
|
||||||
|
function attachURL(url, callback) {
|
||||||
|
var win = window.open(url, "_blank");
|
||||||
|
var client = null;
|
||||||
|
|
||||||
|
let cleanup = () => {
|
||||||
|
if (client) {
|
||||||
|
client.close();
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
if (win) {
|
||||||
|
win.close();
|
||||||
|
win = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gAttachCleanups.push(cleanup);
|
||||||
|
|
||||||
|
window.addEventListener("message", function loadListener(event) {
|
||||||
|
if (event.data === "ready") {
|
||||||
|
client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
client.connect((applicationType, traits) => {
|
||||||
|
client.listTabs(response => {
|
||||||
|
for (let tab of response.tabs) {
|
||||||
|
if (tab.url === url) {
|
||||||
|
window.removeEventListener("message", loadListener, false);
|
||||||
|
try {
|
||||||
|
callback(null, client, tab, win.document);
|
||||||
|
} catch(ex) {
|
||||||
|
Cu.reportError(ex);
|
||||||
|
dump(ex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
return cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
function promiseOnce(target, event) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
target.on(event, (...args) => {
|
||||||
|
if (args.length === 1) {
|
||||||
|
deferred.resolve(args[0]);
|
||||||
|
} else {
|
||||||
|
deferred.resolve(args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortOwnershipChildren(children) {
|
||||||
|
return children.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverOwnershipSubtree(walker, node) {
|
||||||
|
let actor = walker._refMap.get(node);
|
||||||
|
if (!actor) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let children = [];
|
||||||
|
let docwalker = _documentWalker(node);
|
||||||
|
let child = docwalker.firstChild();
|
||||||
|
while (child) {
|
||||||
|
let item = serverOwnershipSubtree(walker, child);
|
||||||
|
if (item) {
|
||||||
|
children.push(item);
|
||||||
|
}
|
||||||
|
child = docwalker.nextSibling();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: actor.actorID,
|
||||||
|
children: sortOwnershipChildren(children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverOwnershipTree(walker) {
|
||||||
|
let serverConnection = walker.conn._transport._serverConnection;
|
||||||
|
let serverWalker = serverConnection.getActor(walker.actorID);
|
||||||
|
|
||||||
|
return {
|
||||||
|
root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc ),
|
||||||
|
orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)],
|
||||||
|
retained: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._retainedOrphans)]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientOwnershipSubtree(node) {
|
||||||
|
return {
|
||||||
|
name: node.actorID,
|
||||||
|
children: sortOwnershipChildren([clientOwnershipSubtree(child) for (child of node.treeChildren())])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientOwnershipTree(walker) {
|
||||||
|
return {
|
||||||
|
root: clientOwnershipSubtree(walker.rootNode),
|
||||||
|
orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)],
|
||||||
|
retained: [clientOwnershipSubtree(o) for (o of walker._retainedOrphans)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ownershipTreeSize(tree) {
|
||||||
|
let size = 1;
|
||||||
|
for (let child of tree.children) {
|
||||||
|
size += ownershipTreeSize(child);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertOwnershipTrees(walker) {
|
||||||
|
let serverTree = serverOwnershipTree(walker);
|
||||||
|
let clientTree = clientOwnershipTree(walker);
|
||||||
|
is(JSON.stringify(clientTree, null, ' '), JSON.stringify(serverTree, null, ' '), "Server and client ownership trees should match.");
|
||||||
|
|
||||||
|
return ownershipTreeSize(clientTree.root);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that an actorID is inaccessible both from the client library and the server.
|
||||||
|
function checkMissing(client, actorID) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
let front = client.getActor(actorID);
|
||||||
|
ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID);
|
||||||
|
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
client.request({
|
||||||
|
to: actorID,
|
||||||
|
type: "request",
|
||||||
|
}, response => {
|
||||||
|
is(response.error, "noSuchActor", "node list actor should no longer be contactable.");
|
||||||
|
deferred.resolve(undefined);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that an actorID is accessible both from the client library and the server.
|
||||||
|
function checkAvailable(client, actorID) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
let front = client.getActor(actorID);
|
||||||
|
ok(front, "Front should be accessible from the client for actorID: " + actorID);
|
||||||
|
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
client.request({
|
||||||
|
to: actorID,
|
||||||
|
type: "garbageAvailableTest",
|
||||||
|
}, response => {
|
||||||
|
is(response.error, "unrecognizedPacketType", "node list actor should be contactable.");
|
||||||
|
deferred.resolve(undefined);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function promiseDone(promise) {
|
||||||
|
promise.then(null, err => {
|
||||||
|
ok(false, "Promise failed: " + err);
|
||||||
|
if (err.stack) {
|
||||||
|
dump(err.stack);
|
||||||
|
}
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation list testing
|
||||||
|
|
||||||
|
function isSrcChange(change) {
|
||||||
|
return (change.type === "attributes" && change.attributeName === "src");
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertAndStrip(mutations, message, test) {
|
||||||
|
let size = mutations.length;
|
||||||
|
mutations = mutations.filter(test);
|
||||||
|
ok((mutations.size != size), message);
|
||||||
|
return mutations;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSrcChange(change) {
|
||||||
|
return change.type === "attributes" && change.attributeName === "src";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUnload(change) {
|
||||||
|
return change.type === "documentUnload";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFrameLoad(change) {
|
||||||
|
return change.type === "frameLoad";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUnretained(change) {
|
||||||
|
return change.type === "unretained";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isChildList(change) {
|
||||||
|
return change.type === "childList";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure an iframe's src attribute changed and then
|
||||||
|
// strip that mutation out of the list.
|
||||||
|
function assertSrcChange(mutations) {
|
||||||
|
return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there's an unload in the mutation list and strip
|
||||||
|
// that mutation out of the list
|
||||||
|
function assertUnload(mutations) {
|
||||||
|
return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there's a frame load in the mutation list and strip
|
||||||
|
// that mutation out of the list
|
||||||
|
function assertFrameLoad(mutations) {
|
||||||
|
return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there's a childList change in the mutation list and strip
|
||||||
|
// that mutation out of the list
|
||||||
|
function assertChildList(mutations) {
|
||||||
|
return assertAndStrip(mutations, "Should have had a frame load change.", isChildList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load mutations aren't predictable, so keep accumulating mutations until
|
||||||
|
// the one we're looking for shows up.
|
||||||
|
function waitForMutation(walker, test, mutations=[]) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
for (let change of mutations) {
|
||||||
|
if (test(change)) {
|
||||||
|
deferred.resolve(mutations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walker.once("mutations", newMutations => {
|
||||||
|
waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => {
|
||||||
|
deferred.resolve(finalMutations);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var _tests = [];
|
||||||
|
function addTest(test) {
|
||||||
|
_tests.push(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runNextTest() {
|
||||||
|
if (_tests.length == 0) {
|
||||||
|
SimpleTest.finish()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tests.shift()();
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.onload = function() {
|
||||||
|
// Put a copy of the body in an iframe to test frame traversal.
|
||||||
|
var body = document.querySelector("body");
|
||||||
|
var data = "data:text/html,<html>" + body.outerHTML + "<html>";
|
||||||
|
var iframe = document.createElement("iframe");
|
||||||
|
iframe.setAttribute("id", "childFrame");
|
||||||
|
iframe.onload = function() {
|
||||||
|
window.opener.postMessage('ready', '*')
|
||||||
|
};
|
||||||
|
iframe.src = data;
|
||||||
|
body.appendChild(iframe);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<body>
|
||||||
|
<h1>Inspector Actor Tests</h1>
|
||||||
|
<span id="longstring">longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong</span>
|
||||||
|
<span id="shortstring">short</span>
|
||||||
|
<span id="empty"></span>
|
||||||
|
<div id="longlist" data-test="exists">
|
||||||
|
<div id="a">a</div>
|
||||||
|
<div id="b">b</div>
|
||||||
|
<div id="c">c</div>
|
||||||
|
<div id="d">d</div>
|
||||||
|
<div id="e">e</div>
|
||||||
|
<div id="f">f</div>
|
||||||
|
<div id="g">g</div>
|
||||||
|
<div id="h">h</div>
|
||||||
|
<div id="i">i</div>
|
||||||
|
<div id="j">j</div>
|
||||||
|
<div id="k">k</div>
|
||||||
|
<div id="l">l</div>
|
||||||
|
<div id="m">m</div>
|
||||||
|
<div id="n">n</div>
|
||||||
|
<div id="o">o</div>
|
||||||
|
<div id="p">p</div>
|
||||||
|
<div id="q">q</div>
|
||||||
|
<div id="r">r</div>
|
||||||
|
<div id="s">s</div>
|
||||||
|
<div id="t">t</div>
|
||||||
|
<div id="u">u</div>
|
||||||
|
<div id="v">v</div>
|
||||||
|
<div id="w">w</div>
|
||||||
|
<div id="x">x</div>
|
||||||
|
<div id="y">y</div>
|
||||||
|
<div id="z">z</div>
|
||||||
|
</div>
|
||||||
|
<div id="longlist-sibling">
|
||||||
|
<div id="longlist-sibling-firstchild"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,5 @@
|
||||||
|
# vim: set filetype=python:
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<script>
|
||||||
|
var xhr = new XMLHttpRequest;
|
||||||
|
xhr.timeout = 1742;
|
||||||
|
xhr.expando = 'Expando!';
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -0,0 +1,102 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gClient = null;
|
||||||
|
var gWalker = null;
|
||||||
|
var checkActorIDs = [];
|
||||||
|
|
||||||
|
function assertOwnership() {
|
||||||
|
assertOwnershipTrees(gWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function setup() {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
ok(walker, "getWalker() should return an actor.");
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testChangeAttrs() {
|
||||||
|
let attrNode = gInspectee.querySelector("#a");
|
||||||
|
let attrFront;
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
|
||||||
|
attrFront = front;
|
||||||
|
dump("attrFront is: " + attrFront + "\n");
|
||||||
|
// Add a few attributes.
|
||||||
|
let list = attrFront.startModifyingAttributes();
|
||||||
|
list.setAttribute("data-newattr", "newvalue");
|
||||||
|
list.setAttribute("data-newattr2", "newvalue");
|
||||||
|
return list.apply();
|
||||||
|
}).then(() => {
|
||||||
|
// We're only going to test that the change hit the document.
|
||||||
|
// There are other tests that make sure changes are propagated
|
||||||
|
// to the client.
|
||||||
|
is(attrNode.getAttribute("data-newattr"), "newvalue", "Node should have the first new attribute");
|
||||||
|
is(attrNode.getAttribute("data-newattr2"), "newvalue", "Node should have the second new attribute.");
|
||||||
|
}).then(() => {
|
||||||
|
// Change an attribute.
|
||||||
|
let list = attrFront.startModifyingAttributes();
|
||||||
|
list.setAttribute("data-newattr", "changedvalue");
|
||||||
|
return list.apply();
|
||||||
|
}).then(() => {
|
||||||
|
is(attrNode.getAttribute("data-newattr"), "changedvalue", "Node should have the changed first value.");
|
||||||
|
is(attrNode.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
|
||||||
|
}).then(() => {
|
||||||
|
let list = attrFront.startModifyingAttributes();
|
||||||
|
list.removeAttribute("data-newattr2");
|
||||||
|
return list.apply();
|
||||||
|
}).then(() => {
|
||||||
|
is(attrNode.getAttribute("data-newattr"), "changedvalue", "Node should have the changed first value.");
|
||||||
|
ok(!attrNode.hasAttribute("data-newattr2"), "Second value should be removed.");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gWalker;
|
||||||
|
delete gInspectee;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,84 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gClient = null;
|
||||||
|
var gWalker = null;
|
||||||
|
|
||||||
|
function assertOwnership() {
|
||||||
|
assertOwnershipTrees(gWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function setup() {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
ok(walker, "getWalker() should return an actor.");
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testChangeValue() {
|
||||||
|
let contentNode = gInspectee.querySelector("#a").firstChild;
|
||||||
|
let nodeFront;
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
|
||||||
|
// Get the text child
|
||||||
|
return gWalker.children(front, { maxNodes: 1 });
|
||||||
|
}).then(children => {
|
||||||
|
nodeFront = children.nodes[0];
|
||||||
|
is(nodeFront.nodeType, Ci.nsIDOMNode.TEXT_NODE);
|
||||||
|
return nodeFront.setNodeValue("newvalue");
|
||||||
|
}).then(() => {
|
||||||
|
// We're only going to test that the change hit the document.
|
||||||
|
// There are other tests that make sure changes are propagated
|
||||||
|
// to the client.
|
||||||
|
is(contentNode.nodeValue, "newvalue", "Node should have a new value.");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gWalker;
|
||||||
|
delete gInspectee;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,127 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gWalker = null;
|
||||||
|
var gClient = null;
|
||||||
|
var attrNode;
|
||||||
|
var attrFront;
|
||||||
|
|
||||||
|
addTest(function setup() {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
ok(walker, "getWalker() should return an actor.");
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(setupAttrTest);
|
||||||
|
addTest(testAddAttribute);
|
||||||
|
addTest(testChangeAttribute);
|
||||||
|
addTest(testRemoveAttribute);
|
||||||
|
addTest(setupFrameAttrTest);
|
||||||
|
addTest(testAddAttribute);
|
||||||
|
addTest(testChangeAttribute);
|
||||||
|
addTest(testRemoveAttribute);
|
||||||
|
|
||||||
|
function setupAttrTest() {
|
||||||
|
attrNode = gInspectee.querySelector("#a")
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
|
||||||
|
attrFront = node;
|
||||||
|
}).then(runNextTest));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupFrameAttrTest() {
|
||||||
|
let frame = gInspectee.querySelector('#childFrame');
|
||||||
|
attrNode = frame.contentDocument.querySelector("#a");
|
||||||
|
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
|
||||||
|
return gWalker.children(childFrame);
|
||||||
|
}).then(children => {
|
||||||
|
let nodes = children.nodes;
|
||||||
|
ok(nodes.length, 1, "There should be only one child of the iframe");
|
||||||
|
is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
|
||||||
|
return gWalker.querySelector(nodes[0], "#a");
|
||||||
|
}).then(node => {
|
||||||
|
attrFront = node;
|
||||||
|
}).then(runNextTest));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAddAttribute() {
|
||||||
|
attrNode.setAttribute("data-newattr", "newvalue");
|
||||||
|
attrNode.setAttribute("data-newattr2", "newvalue");
|
||||||
|
gWalker.once("mutations", () => {
|
||||||
|
is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
|
||||||
|
is(attrFront.getAttribute("data-newattr"), "newvalue", "Node front should have the first new attribute");
|
||||||
|
is(attrFront.getAttribute("data-newattr2"), "newvalue", "Node front should have the second new attribute.");
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testChangeAttribute() {
|
||||||
|
attrNode.setAttribute("data-newattr", "changedvalue");
|
||||||
|
gWalker.once("mutations", () => {
|
||||||
|
is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
|
||||||
|
is(attrFront.getAttribute("data-newattr"), "changedvalue", "Node front should have the changed first value");
|
||||||
|
is(attrFront.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRemoveAttribute() {
|
||||||
|
attrNode.removeAttribute("data-newattr2");
|
||||||
|
gWalker.once("mutations", () => {
|
||||||
|
is(attrFront.attributes.length, 2, "Should have id and one remaining attribute.");
|
||||||
|
is(attrFront.getAttribute("data-newattr"), "changedvalue", "Node front should still have the first value");
|
||||||
|
ok(!attrFront.hasAttribute("data-newattr2"), "Second value should be removed.");
|
||||||
|
runNextTest();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gInspectee;
|
||||||
|
delete gWalker;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,313 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gWalker = null;
|
||||||
|
var gClient = null;
|
||||||
|
var gCleanupConnection = null;
|
||||||
|
|
||||||
|
function setup(callback) {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(callback));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
gWalker = null;
|
||||||
|
gClient = null;
|
||||||
|
gInspectee = null;
|
||||||
|
if (gCleanupConnection) {
|
||||||
|
gCleanupConnection();
|
||||||
|
gCleanupConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertOwnership() {
|
||||||
|
let num = assertOwnershipTrees(gWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setParent(nodeSelector, newParentSelector) {
|
||||||
|
let node = gInspectee.querySelector(nodeSelector);
|
||||||
|
if (newParentSelector) {
|
||||||
|
let newParent = gInspectee.querySelector(newParentSelector);
|
||||||
|
newParent.appendChild(node);
|
||||||
|
} else {
|
||||||
|
node.parentNode.removeChild(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSelector(selector) {
|
||||||
|
return gWalker.querySelectorAll(gWalker.rootNode, selector).then(nodeList => {
|
||||||
|
return nodeList.items();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSelectors(selectors) {
|
||||||
|
return Promise.all([loadSelector(sel) for (sel of selectors)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doMoves(moves) {
|
||||||
|
for (let move of moves) {
|
||||||
|
setParent(move[0], move[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test a set of tree rearrangements and make sure they cause the expected changes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDummySerial = 0;
|
||||||
|
|
||||||
|
function mutationTest(testSpec) {
|
||||||
|
return function() {
|
||||||
|
setup(() => {
|
||||||
|
promiseDone(loadSelectors(testSpec.load || ["html"]).then(() => {
|
||||||
|
gWalker.autoCleanup = !!testSpec.autoCleanup;
|
||||||
|
if (testSpec.preCheck) {
|
||||||
|
testSpec.preCheck();
|
||||||
|
}
|
||||||
|
doMoves(testSpec.moves || []);
|
||||||
|
|
||||||
|
// Some of these moves will trigger no mutation events,
|
||||||
|
// so do a dummy change to the root node to trigger
|
||||||
|
// a mutation event anyway.
|
||||||
|
gInspectee.documentElement.setAttribute("data-dummy", gDummySerial++);
|
||||||
|
|
||||||
|
gWalker.once("mutations", (mutations) => {
|
||||||
|
// Filter out our dummy mutation.
|
||||||
|
let mutations = mutations.filter(change => {
|
||||||
|
if (change.type == "attributes" &&
|
||||||
|
change.attributeName == "data-dummy") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
assertOwnership();
|
||||||
|
if (testSpec.postCheck) {
|
||||||
|
testSpec.postCheck(mutations);
|
||||||
|
}
|
||||||
|
teardown();
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that our dummy mutation works.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 0, "Dummy mutation is filtered out.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Test a simple move to a different location in the sibling list for the same
|
||||||
|
// parent.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
load: ["#longlist div"],
|
||||||
|
moves: [
|
||||||
|
["#a", "#longlist"]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
let remove = mutations[0];
|
||||||
|
is(remove.type, "childList", "First mutation should be a childList.")
|
||||||
|
ok(remove.removed.length > 0, "First mutation should be a removal.")
|
||||||
|
let add = mutations[1];
|
||||||
|
is(add.type, "childList", "Second mutation should be a childList removal.")
|
||||||
|
ok(add.added.length > 0, "Second mutation should be an addition.")
|
||||||
|
let a = add.added[0];
|
||||||
|
is(a.id, "a", "Added node should be #a");
|
||||||
|
is(a.parentNode(), remove.target, "Should still be a child of longlist.");
|
||||||
|
is(remove.target, add.target, "First and second mutations should be against the same node.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Test a move to another location that is within our ownership tree.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
load: ["#longlist div", "#longlist-sibling"],
|
||||||
|
moves: [
|
||||||
|
["#a", "#longlist-sibling"]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
let remove = mutations[0];
|
||||||
|
is(remove.type, "childList", "First mutation should be a childList.")
|
||||||
|
ok(remove.removed.length > 0, "First mutation should be a removal.")
|
||||||
|
let add = mutations[1];
|
||||||
|
is(add.type, "childList", "Second mutation should be a childList removal.")
|
||||||
|
ok(add.added.length > 0, "Second mutation should be an addition.")
|
||||||
|
let a = add.added[0];
|
||||||
|
is(a.id, "a", "Added node should be #a");
|
||||||
|
is(a.parentNode(), add.target, "Should still be a child of longlist.");
|
||||||
|
is(add.target.id, "longlist-sibling", "long-sibling should be the target.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Move an unseen node with a seen parent into our ownership tree - should generate a
|
||||||
|
// childList pair with no adds or removes.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
load: ["#longlist"],
|
||||||
|
moves: [
|
||||||
|
["#longlist-sibling", "#longlist"]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 2, "Should generate two mutations");
|
||||||
|
is(mutations[0].type, "childList", "Should be childList mutations.");
|
||||||
|
is(mutations[0].added.length, 0, "Should have no adds.");
|
||||||
|
is(mutations[0].removed.length, 0, "Should have no removes.");
|
||||||
|
is(mutations[1].type, "childList", "Should be childList mutations.");
|
||||||
|
is(mutations[1].added.length, 0, "Should have no adds.");
|
||||||
|
is(mutations[1].removed.length, 0, "Should have no removes.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Move an unseen node with an unseen parent into our ownership tree. Should only
|
||||||
|
// generate one childList mutation with no adds or removes.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
load: ["#longlist div"],
|
||||||
|
moves: [
|
||||||
|
["#longlist-sibling-firstchild", "#longlist"]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 1, "Should generate two mutations");
|
||||||
|
is(mutations[0].type, "childList", "Should be childList mutations.");
|
||||||
|
is(mutations[0].added.length, 0, "Should have no adds.");
|
||||||
|
is(mutations[0].removed.length, 0, "Should have no removes.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Move a node between unseen nodes, should generate no mutations.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
load: ["html"],
|
||||||
|
moves: [
|
||||||
|
["#longlist-sibling", "#longlist"]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 0, "Should generate no mutations.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Orphan a node and don't clean it up
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
load: ["#longlist div"],
|
||||||
|
moves: [
|
||||||
|
["#longlist", null]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 1, "Should generate one mutation.");
|
||||||
|
let change = mutations[0];
|
||||||
|
is(change.type, "childList", "Should be a childList.");
|
||||||
|
is(change.removed.length, 1, "Should have removed a child.");
|
||||||
|
let ownership = clientOwnershipTree(gWalker);
|
||||||
|
is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
|
||||||
|
is(ownershipTreeSize(ownership.orphaned[0]), 27, "Should have orphaned longlist and 26 children.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Orphan a node, and do clean it up.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: true,
|
||||||
|
load: ["#longlist div"],
|
||||||
|
moves: [
|
||||||
|
["#longlist", null]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 1, "Should generate one mutation.");
|
||||||
|
let change = mutations[0];
|
||||||
|
is(change.type, "childList", "Should be a childList.");
|
||||||
|
is(change.removed.length, 1, "Should have removed a child.");
|
||||||
|
let ownership = clientOwnershipTree(gWalker);
|
||||||
|
is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Orphan a node by moving it into the tree but out of our visible subtree.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: false,
|
||||||
|
load: ["#longlist div"],
|
||||||
|
moves: [
|
||||||
|
["#longlist", "#longlist-sibling"]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 1, "Should generate one mutation.");
|
||||||
|
let change = mutations[0];
|
||||||
|
is(change.type, "childList", "Should be a childList.");
|
||||||
|
is(change.removed.length, 1, "Should have removed a child.");
|
||||||
|
let ownership = clientOwnershipTree(gWalker);
|
||||||
|
is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
|
||||||
|
is(ownershipTreeSize(ownership.orphaned[0]), 27, "Should have orphaned longlist and 26 children.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Orphan a node by moving it into the tree but out of our visible subtree, and clean it up.
|
||||||
|
addTest(mutationTest({
|
||||||
|
autoCleanup: true,
|
||||||
|
load: ["#longlist div"],
|
||||||
|
moves: [
|
||||||
|
["#longlist", "#longlist-sibling"]
|
||||||
|
],
|
||||||
|
postCheck: function(mutations) {
|
||||||
|
is(mutations.length, 1, "Should generate one mutation.");
|
||||||
|
let change = mutations[0];
|
||||||
|
is(change.type, "childList", "Should be a childList.");
|
||||||
|
is(change.removed.length, 1, "Should have removed a child.");
|
||||||
|
let ownership = clientOwnershipTree(gWalker);
|
||||||
|
is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gInspectee;
|
||||||
|
delete gWalker;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,217 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gWalker = null;
|
||||||
|
var gClient = null;
|
||||||
|
var gChildFrame = null;
|
||||||
|
var gChildDocument = null;
|
||||||
|
var gCleanupConnection = null;
|
||||||
|
|
||||||
|
function setup(callback) {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(callback));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
gWalker = null;
|
||||||
|
gClient = null;
|
||||||
|
gInspectee = null;
|
||||||
|
gChildFrame = null;
|
||||||
|
if (gCleanupConnection) {
|
||||||
|
gCleanupConnection();
|
||||||
|
gCleanupConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertOwnership() {
|
||||||
|
return assertOwnershipTrees(gWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadChildSelector(selector) {
|
||||||
|
return gWalker.querySelector(gWalker.rootNode, "#childFrame").then(frame => {
|
||||||
|
ok(frame.numChildren > 0, "Child frame should consider its loaded document as a child.");
|
||||||
|
gChildFrame = frame;
|
||||||
|
return gWalker.children(frame);
|
||||||
|
}).then(children => {
|
||||||
|
return gWalker.querySelectorAll(children.nodes[0], selector);
|
||||||
|
}).then(nodeList => {
|
||||||
|
return nodeList.items();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUnloadedDoc(mutations) {
|
||||||
|
for (let change of mutations) {
|
||||||
|
if (isUnload(change)) {
|
||||||
|
return change.target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function loadNewChild() {
|
||||||
|
setup(() => {
|
||||||
|
let beforeUnloadSize = 0;
|
||||||
|
// Load a bunch of fronts for actors inside the child frame.
|
||||||
|
promiseDone(loadChildSelector("#longlist div").then(() => {
|
||||||
|
let childFrame = gInspectee.querySelector("#childFrame");
|
||||||
|
childFrame.src = "data:text/html,<html>new child</html>";
|
||||||
|
return waitForMutation(gWalker, isChildList);
|
||||||
|
}).then(mutations => {
|
||||||
|
let unloaded = getUnloadedDoc(mutations);
|
||||||
|
mutations = assertSrcChange(mutations);
|
||||||
|
mutations = assertUnload(mutations);
|
||||||
|
mutations = assertFrameLoad(mutations);
|
||||||
|
mutations = assertChildList(mutations);
|
||||||
|
|
||||||
|
is(mutations.length, 0, "Got the expected mutations.");
|
||||||
|
|
||||||
|
assertOwnership();
|
||||||
|
|
||||||
|
return checkMissing(gClient, unloaded);
|
||||||
|
}).then(() => {
|
||||||
|
teardown();
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function loadNewChildTwice() {
|
||||||
|
setup(() => {
|
||||||
|
let beforeUnloadSize = 0;
|
||||||
|
// Load a bunch of fronts for actors inside the child frame.
|
||||||
|
promiseDone(loadChildSelector("#longlist div").then(() => {
|
||||||
|
let childFrame = gInspectee.querySelector("#childFrame");
|
||||||
|
childFrame.src = "data:text/html,<html>new child</html>";
|
||||||
|
return waitForMutation(gWalker, isChildList);
|
||||||
|
}).then(mutations => {
|
||||||
|
// The first load went through as expected (as tested in loadNewChild)
|
||||||
|
// Now change the source again, but this time we *don't* expect
|
||||||
|
// an unload, because we haven't seen the new child document yet.
|
||||||
|
let childFrame = gInspectee.querySelector("#childFrame");
|
||||||
|
childFrame.src = "data:text/html,<html>second new child</html>";
|
||||||
|
return waitForMutation(gWalker, isChildList);
|
||||||
|
}).then(mutations => {
|
||||||
|
mutations = assertSrcChange(mutations);
|
||||||
|
mutations = assertFrameLoad(mutations);
|
||||||
|
mutations = assertChildList(mutations);
|
||||||
|
ok(!getUnloadedDoc(mutations), "Should not have gotten an unload.");
|
||||||
|
|
||||||
|
is(mutations.length, 0, "Got the expected mutations.");
|
||||||
|
|
||||||
|
assertOwnership();
|
||||||
|
}).then(() => {
|
||||||
|
teardown();
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
addTest(function loadNewChildTwiceAndCareAboutIt() {
|
||||||
|
setup(() => {
|
||||||
|
let beforeUnloadSize = 0;
|
||||||
|
// Load a bunch of fronts for actors inside the child frame.
|
||||||
|
promiseDone(loadChildSelector("#longlist div").then(() => {
|
||||||
|
let childFrame = gInspectee.querySelector("#childFrame");
|
||||||
|
childFrame.src = "data:text/html,<html>new child</html>";
|
||||||
|
return waitForMutation(gWalker, isChildList);
|
||||||
|
}).then(mutations => {
|
||||||
|
// Read the new child
|
||||||
|
return loadChildSelector("#longlist div");
|
||||||
|
}).then(() => {
|
||||||
|
// Now change the source again, and expect the same results as loadNewChild.
|
||||||
|
let childFrame = gInspectee.querySelector("#childFrame");
|
||||||
|
childFrame.src = "data:text/html,<html>second new child</html>";
|
||||||
|
return waitForMutation(gWalker, isChildList);
|
||||||
|
}).then(mutations => {
|
||||||
|
let unloaded = getUnloadedDoc(mutations);
|
||||||
|
|
||||||
|
mutations = assertSrcChange(mutations);
|
||||||
|
mutations = assertUnload(mutations);
|
||||||
|
mutations = assertFrameLoad(mutations);
|
||||||
|
mutations = assertChildList(mutations);
|
||||||
|
|
||||||
|
is(mutations.length, 0, "Got the expected mutations.");
|
||||||
|
|
||||||
|
assertOwnership();
|
||||||
|
|
||||||
|
return checkMissing(gClient, unloaded);
|
||||||
|
}).then(() => {
|
||||||
|
teardown();
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testBack() {
|
||||||
|
setup(() => {
|
||||||
|
let beforeUnloadSize = 0;
|
||||||
|
// Load a bunch of fronts for actors inside the child frame.
|
||||||
|
promiseDone(loadChildSelector("#longlist div").then(() => {
|
||||||
|
let childFrame = gInspectee.querySelector("#childFrame");
|
||||||
|
childFrame.src = "data:text/html,<html>new child</html>";
|
||||||
|
return waitForMutation(gWalker, isChildList);
|
||||||
|
}).then(mutations => {
|
||||||
|
// Read the new child
|
||||||
|
return loadChildSelector("#longlist div");
|
||||||
|
}).then(() => {
|
||||||
|
// Now use history.back to change the source, and expect the same results as loadNewChild.
|
||||||
|
let childFrame = gInspectee.querySelector("#childFrame");
|
||||||
|
childFrame.contentWindow.history.back();
|
||||||
|
return waitForMutation(gWalker, isChildList);
|
||||||
|
}).then(mutations => {
|
||||||
|
let unloaded = getUnloadedDoc(mutations);
|
||||||
|
mutations = assertSrcChange(mutations);
|
||||||
|
mutations = assertUnload(mutations);
|
||||||
|
mutations = assertFrameLoad(mutations);
|
||||||
|
mutations = assertChildList(mutations);
|
||||||
|
is(mutations.length, 0, "Got the expected mutations.");
|
||||||
|
|
||||||
|
assertOwnership();
|
||||||
|
|
||||||
|
return checkMissing(gClient, unloaded);
|
||||||
|
}).then(() => {
|
||||||
|
teardown();
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,151 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSummaryLength = 10;
|
||||||
|
inspector.setValueSummaryLength(testSummaryLength);
|
||||||
|
SimpleTest.registerCleanupFunction(function() {
|
||||||
|
inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
|
||||||
|
});
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gWalker = null;
|
||||||
|
var gClient = null;
|
||||||
|
var valueNode;
|
||||||
|
var valueFront;
|
||||||
|
var longString = "stringstringstringstringstringstringstringstringstringstringstring";
|
||||||
|
var truncatedLongString = longString.substring(0, testSummaryLength);
|
||||||
|
var shortString = "str";
|
||||||
|
var shortString2 = "str2";
|
||||||
|
|
||||||
|
addTest(function setup() {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
ok(walker, "getWalker() should return an actor.");
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(setupValueTest);
|
||||||
|
addTest(testKeepLongValue);
|
||||||
|
addTest(testSetShortValue);
|
||||||
|
addTest(testKeepShortValue);
|
||||||
|
addTest(testSetLongValue);
|
||||||
|
addTest(setupFrameValueTest);
|
||||||
|
addTest(testKeepLongValue);
|
||||||
|
addTest(testSetShortValue);
|
||||||
|
addTest(testKeepShortValue);
|
||||||
|
addTest(testSetLongValue);
|
||||||
|
|
||||||
|
function setupValueTest() {
|
||||||
|
valueNode = gInspectee.querySelector("#longstring").firstChild;
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
|
||||||
|
return gWalker.children(node);
|
||||||
|
}).then(children => {
|
||||||
|
valueFront = children.nodes[0];
|
||||||
|
}).then(runNextTest));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupFrameValueTest() {
|
||||||
|
let frame = gInspectee.querySelector('#childFrame');
|
||||||
|
valueNode = frame.contentDocument.querySelector("#longstring").firstChild;
|
||||||
|
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
|
||||||
|
return gWalker.children(childFrame);
|
||||||
|
}).then(children => {
|
||||||
|
let nodes = children.nodes;
|
||||||
|
ok(nodes.length, 1, "There should be only one child of the iframe");
|
||||||
|
is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
|
||||||
|
return gWalker.querySelector(nodes[0], "#longstring");
|
||||||
|
}).then(node => {
|
||||||
|
return gWalker.children(node);
|
||||||
|
}).then(children => {
|
||||||
|
valueFront = children.nodes[0];
|
||||||
|
}).then(runNextTest));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testKeepLongValue() {
|
||||||
|
// After first setup we should have a long string in the node
|
||||||
|
is(valueFront.shortValue.length, testSummaryLength, "After setup the test node should be truncated.");
|
||||||
|
ok(valueFront.incompleteValue, "After setup the node value should be incomplete.");
|
||||||
|
valueNode.nodeValue = longString;
|
||||||
|
gWalker.once("mutations", () => {
|
||||||
|
is(valueFront.shortValue, truncatedLongString, "Value should have changed to a truncated value");
|
||||||
|
ok(valueFront.incompleteValue, "Node value should stay incomplete.");
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSetShortValue() {
|
||||||
|
valueNode.nodeValue = shortString;
|
||||||
|
gWalker.once("mutations", () => {
|
||||||
|
is(valueFront.shortValue, shortString, "Value should not be truncated.");
|
||||||
|
ok(!valueFront.incompleteValue, "Value should not be incomplete.");
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testKeepShortValue() {
|
||||||
|
valueNode.nodeValue = shortString2;
|
||||||
|
gWalker.once("mutations", () => {
|
||||||
|
is(valueFront.shortValue, shortString2, "Value should not be truncated.");
|
||||||
|
ok(!valueFront.incompleteValue, "Value should not be incomplete.");
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSetLongValue() {
|
||||||
|
valueNode.nodeValue = longString;
|
||||||
|
gWalker.once("mutations", () => {
|
||||||
|
is(valueFront.shortValue, truncatedLongString, "Value should have changed to a truncated value");
|
||||||
|
ok(valueFront.incompleteValue, "Node value should stay incomplete.");
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gInspectee;
|
||||||
|
delete gWalker;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,177 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
|
||||||
|
getService(Components.interfaces.inIDOMUtils);
|
||||||
|
|
||||||
|
const KNOWN_PSEUDOCLASSES = [':hover', ':active', ':focus']
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gWalker = null;
|
||||||
|
var gClient = null;
|
||||||
|
var gCleanupConnection = null;
|
||||||
|
|
||||||
|
function setup(callback) {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(callback));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
gWalker = null;
|
||||||
|
gClient = null;
|
||||||
|
gInspectee = null;
|
||||||
|
if (gCleanupConnection) {
|
||||||
|
gCleanupConnection();
|
||||||
|
gCleanupConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkChange(change, expectation) {
|
||||||
|
is(change.type, "pseudoClassLock", "Expect a pseudoclass lock change.");
|
||||||
|
let target = change.target;
|
||||||
|
if (expectation.id)
|
||||||
|
is(target.id, expectation.id, "Expect a change on node id " + expectation.id);
|
||||||
|
if (expectation.nodeName)
|
||||||
|
is(target.nodeName, expectation.nodeName, "Expect a change on node name " + expectation.nodeName);
|
||||||
|
|
||||||
|
is(target.pseudoClassLocks.length, expectation.pseudos.length,
|
||||||
|
"Expect " + expectation.pseudos.length + " pseudoclass locks.");
|
||||||
|
for (let pseudo of expectation.pseudos) {
|
||||||
|
ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo);
|
||||||
|
ok(DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Expect lock in dom: " + pseudo);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let pseudo of KNOWN_PSEUDOCLASSES) {
|
||||||
|
if (!expectation.pseudos.some(expected => pseudo === expected)) {
|
||||||
|
ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo);
|
||||||
|
ok(!DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Don't expect lock in dom: " + pseudo);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMutations(mutations, expectations) {
|
||||||
|
is(mutations.length, expectations.length, "Should get the right number of mutations.");
|
||||||
|
for (let i = 0; i < mutations.length; i++) {
|
||||||
|
checkChange(mutations[i] , expectations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function testPseudoClassLock() {
|
||||||
|
let contentNode;
|
||||||
|
let nodeFront;
|
||||||
|
setup(() => {
|
||||||
|
contentNode = gInspectee.querySelector("#b");
|
||||||
|
return promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(front => {
|
||||||
|
nodeFront = front;
|
||||||
|
// Lock the pseudoclass alone, no parents.
|
||||||
|
gWalker.addPseudoClassLock(nodeFront, ':active');
|
||||||
|
// Expect a single pseudoClassLock mutation.
|
||||||
|
return promiseOnce(gWalker, "mutations");
|
||||||
|
}).then(mutations => {
|
||||||
|
is(mutations.length, 1, "Should get one mutations");
|
||||||
|
is(mutations[0].target, nodeFront, "Should be the node we tried to apply to");
|
||||||
|
checkChange(mutations[0], {
|
||||||
|
id: "b",
|
||||||
|
nodeName: "DIV",
|
||||||
|
pseudos: [":active"]
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
// Now add :hover, this time with parents.
|
||||||
|
gWalker.addPseudoClassLock(nodeFront, ':hover', {parents: true});
|
||||||
|
return promiseOnce(gWalker, "mutations");
|
||||||
|
}).then(mutations => {
|
||||||
|
let expectedMutations = [{
|
||||||
|
id: 'b',
|
||||||
|
nodeName: 'DIV',
|
||||||
|
pseudos: [':hover', ':active'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'longlist',
|
||||||
|
nodeName: 'DIV',
|
||||||
|
pseudos: [':hover']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: 'BODY',
|
||||||
|
pseudos: [':hover']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: 'HTML',
|
||||||
|
pseudos: [':hover']
|
||||||
|
}];
|
||||||
|
checkMutations(mutations, expectedMutations);
|
||||||
|
}).then(() => {
|
||||||
|
// Now remove the :hover on all parents
|
||||||
|
gWalker.removePseudoClassLock(nodeFront, ':hover', {parents: true});
|
||||||
|
return promiseOnce(gWalker, "mutations");
|
||||||
|
}).then(mutations => {
|
||||||
|
let expectedMutations = [{
|
||||||
|
id: 'b',
|
||||||
|
nodeName: 'DIV',
|
||||||
|
// Should still have :active on the original node.
|
||||||
|
pseudos: [':active']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'longlist',
|
||||||
|
nodeName: 'DIV',
|
||||||
|
pseudos: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: 'BODY',
|
||||||
|
pseudos: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: 'HTML',
|
||||||
|
pseudos: []
|
||||||
|
}];
|
||||||
|
checkMutations(mutations, expectedMutations);
|
||||||
|
}).then(() => {
|
||||||
|
// Now shut down the walker and make sure that clears up the remaining lock.
|
||||||
|
return gWalker.release();
|
||||||
|
}).then(() => {
|
||||||
|
ok(!DOMUtils.hasPseudoClassLock(contentNode, ':active'), "Pseudoclass should have been removed during destruction.");
|
||||||
|
teardown();
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,94 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gWalker = null;
|
||||||
|
var gClient = null;
|
||||||
|
|
||||||
|
function assertOwnership() {
|
||||||
|
return assertOwnershipTrees(gWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function setup() {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
ok(walker, "getWalker() should return an actor.");
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testReleaseSubtree() {
|
||||||
|
let originalOwnershipSize = 0;
|
||||||
|
let longlist = null;
|
||||||
|
let firstChild = null;
|
||||||
|
promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
|
||||||
|
// Make sure we have the 26 children of longlist in our ownership tree.
|
||||||
|
is(list.length, 26, "Expect 26 div children.");
|
||||||
|
// Make sure we've read in all those children and incorporated them in our ownership tree.
|
||||||
|
return list.items();
|
||||||
|
}).then((items)=> {
|
||||||
|
originalOwnershipSize = assertOwnership();
|
||||||
|
ok(originalOwnershipSize > 26, "Should have at least 26 items in our ownership tree");
|
||||||
|
firstChild = items[0].actorID;
|
||||||
|
}).then(() => {
|
||||||
|
// Now get the longlist and release it from the ownership tree.
|
||||||
|
return gWalker.querySelector(gWalker.rootNode, "#longlist");
|
||||||
|
}).then(node => {
|
||||||
|
longlist = node.actorID;
|
||||||
|
return gWalker.releaseNode(node);
|
||||||
|
}).then(() => {
|
||||||
|
// Our ownership size should now be 27 fewer (we forgot about #longlist + 26 children)
|
||||||
|
let newOwnershipSize = assertOwnership();
|
||||||
|
is(newOwnershipSize, originalOwnershipSize - 27, "Ownership tree should have dropped by 27 nodes");
|
||||||
|
// Now verify that some nodes have gone away
|
||||||
|
return checkMissing(gClient, longlist);
|
||||||
|
}).then(() => {
|
||||||
|
return checkMissing(gClient, firstChild);
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gWalker;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,183 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gWalker = null;
|
||||||
|
var gClient = null;
|
||||||
|
var gInspectee = null;
|
||||||
|
|
||||||
|
function assertOwnership() {
|
||||||
|
return assertOwnershipTrees(gWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function setup() {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
ok(walker, "getWalker() should return an actor.");
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Retain a node, and a second-order child (in another document, for kicks)
|
||||||
|
// Release the parent of the top item, which should cause one retained orphan.
|
||||||
|
|
||||||
|
// Then unretain the top node, which should retain the orphan.
|
||||||
|
|
||||||
|
// Then change the source of the iframe, which should kill that orphan.
|
||||||
|
|
||||||
|
addTest(function testRetain() {
|
||||||
|
let originalOwnershipSize = 0;
|
||||||
|
let bodyFront = null;
|
||||||
|
let frameFront = null;
|
||||||
|
let childListFront = null;
|
||||||
|
// Get the toplevel body element and retain it.
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "body").then(front => {
|
||||||
|
bodyFront = front;
|
||||||
|
return gWalker.retainNode(bodyFront);
|
||||||
|
}).then(() => {
|
||||||
|
// Get an element in the child frame and retain it.
|
||||||
|
return gWalker.querySelector(gWalker.rootNode, "#childFrame");
|
||||||
|
}).then(frame => {
|
||||||
|
frameFront = frame;
|
||||||
|
return gWalker.children(frame, { maxNodes: 1 }).then(children => {
|
||||||
|
return children.nodes[0];
|
||||||
|
});
|
||||||
|
}).then(childDoc => {
|
||||||
|
return gWalker.querySelector(childDoc, "#longlist");
|
||||||
|
}).then(list => {
|
||||||
|
childListFront = list;
|
||||||
|
originalOwnershipSize = assertOwnership();
|
||||||
|
// and rtain it.
|
||||||
|
return gWalker.retainNode(childListFront);
|
||||||
|
}).then(() => {
|
||||||
|
// OK, try releasing the parent of the first retained.
|
||||||
|
return gWalker.releaseNode(bodyFront.parentNode());
|
||||||
|
}).then(() => {
|
||||||
|
let size = assertOwnership();
|
||||||
|
let clientTree = clientOwnershipTree(gWalker);
|
||||||
|
|
||||||
|
// That request should have freed the parent of the first retained
|
||||||
|
// but moved the rest into the retained orphaned tree.
|
||||||
|
is(ownershipTreeSize(clientTree.root) + ownershipTreeSize(clientTree.retained[0]) + 1,
|
||||||
|
originalOwnershipSize,
|
||||||
|
"Should have only lost one item overall.");
|
||||||
|
is(gWalker._retainedOrphans.size, 1, "Should have retained one orphan");
|
||||||
|
ok(gWalker._retainedOrphans.has(bodyFront), "Should have retained the expected node.");
|
||||||
|
}).then(() => {
|
||||||
|
// Unretain the body, which should promote the childListFront to a retained orphan.
|
||||||
|
return gWalker.unretainNode(bodyFront);
|
||||||
|
}).then(() => {
|
||||||
|
assertOwnership();
|
||||||
|
let clientTree = clientOwnershipTree(gWalker);
|
||||||
|
|
||||||
|
is(gWalker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
|
||||||
|
ok(!gWalker._retainedOrphans.has(bodyFront), "Should have dropped the body node.")
|
||||||
|
ok(gWalker._retainedOrphans.has(childListFront), "Should have retained the child node.")
|
||||||
|
}).then(() => {
|
||||||
|
// Change the source of the iframe, which should kill the retained orphan.
|
||||||
|
gInspectee.querySelector("#childFrame").src = "data:text/html,<html>new child</html>";
|
||||||
|
return waitForMutation(gWalker, isUnretained);
|
||||||
|
}).then(mutations => {
|
||||||
|
assertOwnership();
|
||||||
|
let clientTree = clientOwnershipTree(gWalker);
|
||||||
|
is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
|
||||||
|
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get a hold of a node, remove it from the doc and retain it at the same time.
|
||||||
|
// We should always win that race (even though the mutation happens before the
|
||||||
|
// retain request), because we haven't issued `getMutations` yet.
|
||||||
|
addTest(function testWinRace() {
|
||||||
|
let front = null;
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
|
||||||
|
front = node;
|
||||||
|
let contentNode = gInspectee.querySelector("#a");
|
||||||
|
contentNode.parentNode.removeChild(contentNode);
|
||||||
|
// Now wait for that mutation and retain response to come in.
|
||||||
|
return Promise.all([
|
||||||
|
gWalker.retainNode(front),
|
||||||
|
waitForMutation(gWalker, isChildList)
|
||||||
|
]);
|
||||||
|
}).then(() => {
|
||||||
|
assertOwnership();
|
||||||
|
let clientTree = clientOwnershipTree(gWalker);
|
||||||
|
is(gWalker._retainedOrphans.size, 1, "Should have a retained orphan.");
|
||||||
|
ok(gWalker._retainedOrphans.has(front), "Should have retained our expected node.");
|
||||||
|
return gWalker.unretainNode(front);
|
||||||
|
}).then(() => {
|
||||||
|
// Make sure we're clear for the next test.
|
||||||
|
assertOwnership();
|
||||||
|
let clientTree = clientOwnershipTree(gWalker);
|
||||||
|
is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Same as above, but issue the request right after the 'new-mutations' event, so that
|
||||||
|
// we *lose* the race.
|
||||||
|
addTest(function testLoseRace() {
|
||||||
|
let front = null;
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#z").then(node => {
|
||||||
|
front = node;
|
||||||
|
gInspectee.querySelector("#z").parentNode = null;
|
||||||
|
let contentNode = gInspectee.querySelector("#a");
|
||||||
|
contentNode.parentNode.removeChild(contentNode);
|
||||||
|
return promiseOnce(gWalker, "new-mutations");
|
||||||
|
}).then(() => {
|
||||||
|
// Verify that we have an outstanding request (no good way to tell that it's a
|
||||||
|
// getMutations request, but there's nothing else it would be).
|
||||||
|
is(gWalker._requests.length, 1, "Should have an outstanding request.");
|
||||||
|
return gWalker.retainNode(front)
|
||||||
|
}).then(() => { ok(false, "Request should not have succeeded!"); },
|
||||||
|
(err) => {
|
||||||
|
ok(err, "noSuchActor", "Should have lost the race.");
|
||||||
|
let clientTree = clientOwnershipTree(gWalker);
|
||||||
|
is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
|
||||||
|
// Don't re-throw the error.
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gWalker;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,332 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug </title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
const Promise = devtools.require("sdk/core/promise");
|
||||||
|
const inspector = devtools.require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
runNextTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gInspectee = null;
|
||||||
|
var gClient = null;
|
||||||
|
var gWalker = null;
|
||||||
|
var checkActorIDs = [];
|
||||||
|
|
||||||
|
function assertOwnership() {
|
||||||
|
assertOwnershipTrees(gWalker);
|
||||||
|
}
|
||||||
|
addTest(function setup() {
|
||||||
|
let url = document.getElementById("inspectorContent").href;
|
||||||
|
attachURL(url, function(err, client, tab, doc) {
|
||||||
|
gInspectee = doc;
|
||||||
|
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
|
||||||
|
let inspector = InspectorFront(client, tab);
|
||||||
|
promiseDone(inspector.getWalker().then(walker => {
|
||||||
|
ok(walker, "getWalker() should return an actor.");
|
||||||
|
gClient = client;
|
||||||
|
gWalker = walker;
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testWalkerRoot() {
|
||||||
|
// Make sure that refetching the root document of the walker returns the same
|
||||||
|
// actor as the getWalker returned.
|
||||||
|
promiseDone(gWalker.document().then(root => {
|
||||||
|
ok(root === gWalker.rootNode, "Re-fetching the document node should match the root document node.");
|
||||||
|
checkActorIDs.push(root.actorID);
|
||||||
|
assertOwnership();
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testInnerHTML() {
|
||||||
|
promiseDone(gWalker.documentElement().then(docElement => {
|
||||||
|
return gWalker.innerHTML(docElement);
|
||||||
|
}).then(longstring => {
|
||||||
|
return longstring.string();
|
||||||
|
}).then(innerHTML => {
|
||||||
|
ok(innerHTML === gInspectee.documentElement.innerHTML, "innerHTML should match");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testOuterHTML() {
|
||||||
|
promiseDone(gWalker.documentElement().then(docElement => {
|
||||||
|
return gWalker.outerHTML(docElement);
|
||||||
|
}).then(longstring => {
|
||||||
|
return longstring.string();
|
||||||
|
}).then(outerHTML => {
|
||||||
|
ok(outerHTML === gInspectee.documentElement.outerHTML, "outerHTML should match");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testQuerySelector() {
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
|
||||||
|
is(node.getAttribute("data-test"), "exists", "should have found the right node");
|
||||||
|
assertOwnership();
|
||||||
|
}).then(() => {
|
||||||
|
return gWalker.querySelector(gWalker.rootNode, "unknownqueryselector").then(node => {
|
||||||
|
ok(!node, "Should not find a node here.");
|
||||||
|
assertOwnership();
|
||||||
|
});
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testQuerySelectors() {
|
||||||
|
let nodeList = null;
|
||||||
|
let firstNode = null;
|
||||||
|
let nodeListID = null;
|
||||||
|
promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
|
||||||
|
nodeList = list;
|
||||||
|
is(nodeList.length, 26, "Expect 26 div children.");
|
||||||
|
assertOwnership();
|
||||||
|
return nodeList.item(0);
|
||||||
|
}).then(node => {
|
||||||
|
firstNode = node;
|
||||||
|
checkActorIDs.push(node.actorID);
|
||||||
|
is(node.id, "a", "First child should be a");
|
||||||
|
assertOwnership();
|
||||||
|
return nodeList.items();
|
||||||
|
}).then(nodes => {
|
||||||
|
is(nodes.length, 26, "Expect 26 nodes");
|
||||||
|
is(nodes[0], firstNode, "First node should be reused.");
|
||||||
|
ok(nodes[0]._parent, "Parent node should be set.");
|
||||||
|
ok(nodes[0]._next || nodes[0]._prev, "Siblings should be set.");
|
||||||
|
ok(nodes[25]._next || nodes[25]._prev, "Siblings of " + nodes[25] + " should be set.");
|
||||||
|
assertOwnership();
|
||||||
|
return nodeList.items(-1);
|
||||||
|
}).then(nodes => {
|
||||||
|
is(nodes.length, 1, "Expect 1 node")
|
||||||
|
is(nodes[0].id, "z", "Expect it to be the last node.");
|
||||||
|
checkActorIDs.push(nodes[0].actorID);
|
||||||
|
// Save the node list ID so we can ensure it was destroyed.
|
||||||
|
nodeListID = nodeList.actorID;
|
||||||
|
assertOwnership();
|
||||||
|
return nodeList.release();
|
||||||
|
}).then(() => {
|
||||||
|
ok(!nodeList.actorID, "Actor should have been destroyed.");
|
||||||
|
assertOwnership();
|
||||||
|
return checkMissing(gClient, nodeListID);
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper to check the response of requests that return hasFirst/hasLast/nodes
|
||||||
|
// node lists (like `children` and `siblings`)
|
||||||
|
function nodeArrayChecker(first, last, ids) {
|
||||||
|
return function(response) {
|
||||||
|
is(response.hasFirst, first, "Should " + (first ? "" : "not ") + " have the first node.");
|
||||||
|
is(response.hasLast, last, "Should " + (last ? "" : "not ") + " have the last node.");
|
||||||
|
is(response.nodes.length, ids.length, "Should have " + ids.length + " children listed.");
|
||||||
|
let responseIds = '';
|
||||||
|
for (node of response.nodes) {
|
||||||
|
responseIds += node.id;
|
||||||
|
}
|
||||||
|
is(responseIds, ids, "Correct nodes were returned.");
|
||||||
|
assertOwnership();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addTest(function testNoChildren() {
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#empty").then(empty => {
|
||||||
|
assertOwnership();
|
||||||
|
return gWalker.children(empty).then(nodeArrayChecker(true, true, ""));
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testLongListTraversal() {
|
||||||
|
var longList;
|
||||||
|
var allChildren;
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
|
||||||
|
longList = node;
|
||||||
|
// First call with no options, expect all children.
|
||||||
|
assertOwnership();
|
||||||
|
return gWalker.children(longList).then(response => {
|
||||||
|
nodeArrayChecker(true, true, "abcdefghijklmnopqrstuvwxyz")(response);
|
||||||
|
allChildren = response.nodes;
|
||||||
|
assertOwnership();
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
// maxNodes should limit us to the first 5 nodes.
|
||||||
|
assertOwnership();
|
||||||
|
return gWalker.children(longList, { maxNodes: 5 }).then(nodeArrayChecker(true, false, 'abcde'));
|
||||||
|
}).then(() => {
|
||||||
|
assertOwnership();
|
||||||
|
// maxNodes with the second item centered should still give us the first 5 nodes.
|
||||||
|
return gWalker.children(longList, { maxNodes: 5, center: allChildren[1] }).then(
|
||||||
|
nodeArrayChecker(true, false, 'abcde')
|
||||||
|
);
|
||||||
|
}).then(() => {
|
||||||
|
// maxNodes with a center in the middle of the list should put that item in the middle
|
||||||
|
let center = allChildren[13];
|
||||||
|
is(center.id, 'n', "Make sure I know how to count letters.");
|
||||||
|
return gWalker.children(longList, { maxNodes: 5, center: center }).then(
|
||||||
|
nodeArrayChecker(false, false, 'lmnop')
|
||||||
|
);
|
||||||
|
}).then(() => {
|
||||||
|
// maxNodes with the second-to-last item centered should give us the last 5 nodes.
|
||||||
|
return gWalker.children(longList, { maxNodes: 5, center: allChildren[24] }).then(
|
||||||
|
nodeArrayChecker(false, true, 'vwxyz')
|
||||||
|
);
|
||||||
|
}).then(() => {
|
||||||
|
// maxNodes with a start in the middle should start at that node and fetch 5
|
||||||
|
let start = allChildren[13];
|
||||||
|
is(start.id, 'n', "Make sure I know how to count letters.")
|
||||||
|
return gWalker.children(longList, { maxNodes: 5, start: start }).then(
|
||||||
|
nodeArrayChecker(false, false, 'nopqr')
|
||||||
|
);
|
||||||
|
}).then(() => {
|
||||||
|
// maxNodes near the end should only return what's left
|
||||||
|
return gWalker.children(longList, { maxNodes: 5, start: allChildren[24] }).then(
|
||||||
|
nodeArrayChecker(false, true, 'yz')
|
||||||
|
);
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testSiblings() {
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(a => {
|
||||||
|
return gWalker.siblings(a, { maxNodes: 5, center: a }).then(nodeArrayChecker(true, false, "abcde"));
|
||||||
|
}).then(() => {
|
||||||
|
return gWalker.siblings(gWalker.rootNode).then(response => {
|
||||||
|
ok(response.hasFirst && response.hasLast, "Has first and last.");
|
||||||
|
is(response.nodes.length, 1, "Has only the document element.");
|
||||||
|
ok(response.nodes[0] === gWalker.rootNode, "Document element is its own sibling.");
|
||||||
|
});
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testNextSibling() {
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#y").then(y => {
|
||||||
|
is(y.id, "y", "Got the right node.");
|
||||||
|
return gWalker.nextSibling(y);
|
||||||
|
}).then(z => {
|
||||||
|
is(z.id, "z", "nextSibling got the next node.");
|
||||||
|
return gWalker.nextSibling(z);
|
||||||
|
}).then(nothing => {
|
||||||
|
is(nothing, null, "nextSibling on the last node returned null.");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testPreviousSibling() {
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(b => {
|
||||||
|
is(b.id, "b", "Got the right node.");
|
||||||
|
return gWalker.previousSibling(b);
|
||||||
|
}).then(a => {
|
||||||
|
is(a.id, "a", "nextSibling got the next node.");
|
||||||
|
return gWalker.previousSibling(a);
|
||||||
|
}).then(nothing => {
|
||||||
|
is(nothing, null, "previousSibling on the first node returned null.");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
addTest(function testFrameTraversal() {
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
|
||||||
|
return gWalker.children(childFrame);
|
||||||
|
}).then(children => {
|
||||||
|
let nodes = children.nodes;
|
||||||
|
ok(nodes.length, 1, "There should be only one child of the iframe");
|
||||||
|
is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
|
||||||
|
return gWalker.querySelector(nodes[0], "#z");
|
||||||
|
}).then(childDocumentZ => {
|
||||||
|
return gWalker.parents(childDocumentZ);
|
||||||
|
}).then(parents => {
|
||||||
|
// Expected set of parent tag names for this item:
|
||||||
|
let expectedParents = ['DIV', 'BODY', 'HTML', '#document', 'IFRAME', 'BODY', 'HTML', '#document'];
|
||||||
|
for (let parent of parents) {
|
||||||
|
let expected = expectedParents.shift();
|
||||||
|
is(parent.nodeName, expected, "Got expected parent");
|
||||||
|
}
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testLongValue() {
|
||||||
|
const testSummaryLength = 10;
|
||||||
|
inspector.setValueSummaryLength(testSummaryLength);
|
||||||
|
SimpleTest.registerCleanupFunction(function() {
|
||||||
|
inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
|
||||||
|
});
|
||||||
|
|
||||||
|
let longstringText = gInspectee.getElementById("longstring").firstChild.nodeValue;
|
||||||
|
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
|
||||||
|
// Now we need to get the text node child...
|
||||||
|
return gWalker.children(node, { maxNodes: 1 });
|
||||||
|
}).then(children => {
|
||||||
|
let textNode = children.nodes[0];
|
||||||
|
is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
|
||||||
|
is(textNode.shortValue.length, 10, "Value summary should be limited to the summary value length");
|
||||||
|
ok(textNode.incompleteValue, "Value should be incomplete");
|
||||||
|
return textNode;
|
||||||
|
}).then(textNode => {
|
||||||
|
return textNode.getNodeValue();
|
||||||
|
}).then(value => {
|
||||||
|
return value.string();
|
||||||
|
}).then(valueStr => {
|
||||||
|
is(valueStr, longstringText, "Full node value should match the string from the document.");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testShortValue() {
|
||||||
|
let shortstringText = gInspectee.getElementById("shortstring").firstChild.nodeValue;
|
||||||
|
|
||||||
|
promiseDone(gWalker.querySelector(gWalker.rootNode, "#shortstring").then(node => {
|
||||||
|
// Now we need to get the text node child...
|
||||||
|
return gWalker.children(node, { maxNodes: 1 });
|
||||||
|
}).then(children => {
|
||||||
|
let textNode = children.nodes[0];
|
||||||
|
is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
|
||||||
|
is(textNode.shortValue, shortstringText, "Value should be complete");
|
||||||
|
ok(!textNode.incompleteValue, "Value should be complete");
|
||||||
|
return textNode;
|
||||||
|
}).then(textNode => {
|
||||||
|
return textNode.getNodeValue();
|
||||||
|
}).then(value => {
|
||||||
|
return value.string();
|
||||||
|
}).then(valueStr => {
|
||||||
|
is(valueStr, shortstringText, "Full node value should match the string from the document.");
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function testReleaseWalker() {
|
||||||
|
checkActorIDs.push(gWalker.actorID);
|
||||||
|
|
||||||
|
promiseDone(gWalker.release().then(() => {
|
||||||
|
let promises = [checkMissing(gClient, id) for (id of checkActorIDs)];
|
||||||
|
return Promise.all(promises)
|
||||||
|
}).then(runNextTest));
|
||||||
|
});
|
||||||
|
|
||||||
|
addTest(function cleanup() {
|
||||||
|
delete gWalker;
|
||||||
|
delete gInspectee;
|
||||||
|
delete gClient;
|
||||||
|
runNextTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||||
|
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=837723
|
||||||
|
|
||||||
|
When we use Debugger.Object.prototype.unsafeDereference to get a non-D.O
|
||||||
|
reference to a content object in chrome, that reference should be via an
|
||||||
|
xray wrapper.
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Mozilla Bug 837723</title>
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre id="test">
|
||||||
|
<script>
|
||||||
|
|
||||||
|
Components.utils.import("resource://gre/modules/jsdebugger.jsm");
|
||||||
|
addDebuggerToGlobal(this);
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
var iframe = document.createElement("iframe");
|
||||||
|
iframe.src = "http://mochi.test:8888/chrome/toolkit/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html";
|
||||||
|
|
||||||
|
iframe.onload = function () {
|
||||||
|
var dbg = new Debugger;
|
||||||
|
var contentDO = dbg.addDebuggee(iframe.contentWindow);
|
||||||
|
var xhrDesc = contentDO.getOwnPropertyDescriptor('xhr');
|
||||||
|
|
||||||
|
isnot(xhrDesc, undefined, "xhr should be visible as property of content global");
|
||||||
|
isnot(xhrDesc.value, undefined, "xhr should have a value");
|
||||||
|
|
||||||
|
var xhr = xhrDesc.value.unsafeDereference();
|
||||||
|
|
||||||
|
is(typeof xhr, "object", "we should be able to deference xhr's value's D.O");
|
||||||
|
is(xhr.timeout, 1742, "chrome should see the xhr's 'timeout' property");
|
||||||
|
is(xhr.expando, undefined, "chrome should not see the xhr's 'expando' property");
|
||||||
|
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,11 @@
|
||||||
|
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||||
|
# vim: set filetype=python:
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
DIRS += ['mochitest']
|
||||||
|
|
||||||
|
MODULE = 'test_debugger'
|
||||||
|
|
||||||
|
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
|
|
@ -0,0 +1,328 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
const Cc = Components.classes;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
const Cu = Components.utils;
|
||||||
|
const Cr = Components.results;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
// Always log packets when running tests. runxpcshelltests.py will throw
|
||||||
|
// the output away anyway, unless you give it the --verbose flag.
|
||||||
|
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||||
|
// Enable remote debugging for the relevant tests.
|
||||||
|
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
function testExceptionHook(ex) {
|
||||||
|
try {
|
||||||
|
do_report_unexpected_exception(ex);
|
||||||
|
} catch(ex) {
|
||||||
|
return {throw: ex}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an nsIScriptError 'aFlags' value into an appropriate string.
|
||||||
|
function scriptErrorFlagsToKind(aFlags) {
|
||||||
|
var kind;
|
||||||
|
if (aFlags & Ci.nsIScriptError.warningFlag)
|
||||||
|
kind = "warning";
|
||||||
|
if (aFlags & Ci.nsIScriptError.exceptionFlag)
|
||||||
|
kind = "exception";
|
||||||
|
else
|
||||||
|
kind = "error";
|
||||||
|
|
||||||
|
if (aFlags & Ci.nsIScriptError.strictFlag)
|
||||||
|
kind = "strict " + kind;
|
||||||
|
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a console listener, so console messages don't just disappear
|
||||||
|
// into the ether.
|
||||||
|
let errorCount = 0;
|
||||||
|
let listener = {
|
||||||
|
observe: function (aMessage) {
|
||||||
|
errorCount++;
|
||||||
|
var shouldThrow = true;
|
||||||
|
try {
|
||||||
|
// If we've been given an nsIScriptError, then we can print out
|
||||||
|
// something nicely formatted, for tools like Emacs to pick up.
|
||||||
|
var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
|
||||||
|
dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
|
||||||
|
scriptErrorFlagsToKind(aMessage.flags) + ": " +
|
||||||
|
aMessage.errorMessage + "\n");
|
||||||
|
var string = aMessage.errorMessage;
|
||||||
|
shouldThrow = !aMessage.flags;
|
||||||
|
} catch (x) {
|
||||||
|
// Be a little paranoid with message, as the whole goal here is to lose
|
||||||
|
// no information.
|
||||||
|
try {
|
||||||
|
var string = "" + aMessage.message;
|
||||||
|
} catch (x) {
|
||||||
|
var string = "<error converting error message to string>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we exit all nested event loops so that the test can finish.
|
||||||
|
while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
|
||||||
|
DebuggerServer.xpcInspector.exitNestedEventLoop();
|
||||||
|
}
|
||||||
|
if (shouldThrow)
|
||||||
|
do_throw("head_dbg.js got console message: " + string + "\n");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
|
||||||
|
consoleService.registerListener(listener);
|
||||||
|
|
||||||
|
function check_except(func)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
func();
|
||||||
|
} catch (e) {
|
||||||
|
do_check_true(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dump("Should have thrown an exception: " + func.toString());
|
||||||
|
do_check_true(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testGlobal(aName) {
|
||||||
|
let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
|
||||||
|
.createInstance(Ci.nsIPrincipal);
|
||||||
|
|
||||||
|
let sandbox = Cu.Sandbox(systemPrincipal);
|
||||||
|
Cu.evalInSandbox("this.__name = '" + aName + "'", sandbox);
|
||||||
|
return sandbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTestGlobal(aName)
|
||||||
|
{
|
||||||
|
let global = testGlobal(aName);
|
||||||
|
DebuggerServer.addTestGlobal(global);
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List the DebuggerClient |aClient|'s tabs, look for one whose title is
|
||||||
|
// |aTitle|, and apply |aCallback| to the packet's entry for that tab.
|
||||||
|
function getTestTab(aClient, aTitle, aCallback) {
|
||||||
|
aClient.listTabs(function (aResponse) {
|
||||||
|
for (let tab of aResponse.tabs) {
|
||||||
|
if (tab.title === aTitle) {
|
||||||
|
aCallback(tab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aCallback(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the
|
||||||
|
// response packet and a TabClient instance referring to that tab.
|
||||||
|
function attachTestTab(aClient, aTitle, aCallback) {
|
||||||
|
getTestTab(aClient, aTitle, function (aTab) {
|
||||||
|
aClient.attachTab(aTab.actor, aCallback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to |aClient|'s tab whose title is |aTitle|, and then attach to
|
||||||
|
// that tab's thread. Pass |aCallback| the thread attach response packet, a
|
||||||
|
// TabClient referring to the tab, and a ThreadClient referring to the
|
||||||
|
// thread.
|
||||||
|
function attachTestThread(aClient, aTitle, aCallback) {
|
||||||
|
attachTestTab(aClient, aTitle, function (aResponse, aTabClient) {
|
||||||
|
aClient.attachThread(aResponse.threadActor, function (aResponse, aThreadClient) {
|
||||||
|
aCallback(aResponse, aTabClient, aThreadClient);
|
||||||
|
}, { useSourceMaps: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's
|
||||||
|
// thread, and then resume it. Pass |aCallback| the thread's response to
|
||||||
|
// the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
|
||||||
|
// thread.
|
||||||
|
function attachTestTabAndResume(aClient, aTitle, aCallback) {
|
||||||
|
attachTestThread(aClient, aTitle, function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
aThreadClient.resume(function (aResponse) {
|
||||||
|
aCallback(aResponse, aTabClient, aThreadClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the testing debugger server.
|
||||||
|
*/
|
||||||
|
function initTestDebuggerServer()
|
||||||
|
{
|
||||||
|
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
|
||||||
|
DebuggerServer.addActors("resource://test/testactors.js");
|
||||||
|
// Allow incoming connections.
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSourcesBackwardsCompatDebuggerServer()
|
||||||
|
{
|
||||||
|
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js");
|
||||||
|
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
|
||||||
|
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
|
||||||
|
DebuggerServer.addActors("resource://test/testcompatactors.js");
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishClient(aClient)
|
||||||
|
{
|
||||||
|
aClient.close(function() {
|
||||||
|
do_test_finished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a relative file path and returns the absolute file url for it.
|
||||||
|
*/
|
||||||
|
function getFileUrl(aName) {
|
||||||
|
let file = do_get_file(aName);
|
||||||
|
return Services.io.newFileURI(file).spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full path of the file with the specified name in a
|
||||||
|
* platform-independent and URL-like form.
|
||||||
|
*/
|
||||||
|
function getFilePath(aName)
|
||||||
|
{
|
||||||
|
let file = do_get_file(aName);
|
||||||
|
let path = Services.io.newFileURI(file).spec;
|
||||||
|
let filePrePath = "file://";
|
||||||
|
if ("nsILocalFileWin" in Ci &&
|
||||||
|
file instanceof Ci.nsILocalFileWin) {
|
||||||
|
filePrePath += "/";
|
||||||
|
}
|
||||||
|
return path.slice(filePrePath.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full text contents of the given file.
|
||||||
|
*/
|
||||||
|
function readFile(aFileName) {
|
||||||
|
let f = do_get_file(aFileName);
|
||||||
|
let s = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||||
|
.createInstance(Ci.nsIFileInputStream);
|
||||||
|
s.init(f, -1, -1, false);
|
||||||
|
try {
|
||||||
|
return NetUtil.readInputStreamToString(s, s.available());
|
||||||
|
} finally {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile(aFileName, aContent) {
|
||||||
|
let file = do_get_file(aFileName, true);
|
||||||
|
let stream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||||
|
.createInstance(Ci.nsIFileOutputStream);
|
||||||
|
stream.init(file, -1, -1, 0);
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
let numWritten = stream.write(aContent, aContent.length);
|
||||||
|
aContent = aContent.slice(numWritten);
|
||||||
|
} while (aContent.length > 0);
|
||||||
|
} finally {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectPipeTracing() {
|
||||||
|
return new TracingTransport(DebuggerServer.connectPipe());
|
||||||
|
}
|
||||||
|
|
||||||
|
function TracingTransport(childTransport) {
|
||||||
|
this.hooks = null;
|
||||||
|
this.child = childTransport;
|
||||||
|
this.child.hooks = this;
|
||||||
|
|
||||||
|
this.expectations = [];
|
||||||
|
this.packets = [];
|
||||||
|
this.checkIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepEqual(a, b) {
|
||||||
|
if (a === b)
|
||||||
|
return true;
|
||||||
|
if (typeof a != "object" || typeof b != "object")
|
||||||
|
return false;
|
||||||
|
if (a === null || b === null)
|
||||||
|
return false;
|
||||||
|
if (Object.keys(a).length != Object.keys(b).length)
|
||||||
|
return false;
|
||||||
|
for (let k in a) {
|
||||||
|
if (!deepEqual(a[k], b[k]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TracingTransport.prototype = {
|
||||||
|
// Remove actor names
|
||||||
|
normalize: function(packet) {
|
||||||
|
return JSON.parse(JSON.stringify(packet, (key, value) => {
|
||||||
|
if (key === "to" || key === "from" || key === "actor") {
|
||||||
|
return "<actorid>";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
send: function(packet) {
|
||||||
|
this.packets.push({
|
||||||
|
type: "sent",
|
||||||
|
packet: this.normalize(packet)
|
||||||
|
});
|
||||||
|
return this.child.send(packet);
|
||||||
|
},
|
||||||
|
close: function() {
|
||||||
|
return this.child.close();
|
||||||
|
},
|
||||||
|
ready: function() {
|
||||||
|
return this.child.ready();
|
||||||
|
},
|
||||||
|
onPacket: function(packet) {
|
||||||
|
this.packets.push({
|
||||||
|
type: "received",
|
||||||
|
packet: this.normalize(packet)
|
||||||
|
});
|
||||||
|
this.hooks.onPacket(packet);
|
||||||
|
},
|
||||||
|
onClosed: function() {
|
||||||
|
this.hooks.onClosed();
|
||||||
|
},
|
||||||
|
|
||||||
|
expectSend: function(expected) {
|
||||||
|
let packet = this.packets[this.checkIndex++];
|
||||||
|
do_check_eq(packet.type, "sent");
|
||||||
|
do_check_true(deepEqual(packet.packet, this.normalize(expected)));
|
||||||
|
},
|
||||||
|
|
||||||
|
expectReceive: function(expected) {
|
||||||
|
let packet = this.packets[this.checkIndex++];
|
||||||
|
do_check_eq(packet.type, "received");
|
||||||
|
do_check_true(deepEqual(packet.packet, this.normalize(expected)));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Write your tests, call dumpLog at the end, inspect the output,
|
||||||
|
// then sprinkle the calls through the right places in your test.
|
||||||
|
dumpLog: function() {
|
||||||
|
for (let entry of this.packets) {
|
||||||
|
if (entry.type === "sent") {
|
||||||
|
dump("trace.expectSend(" + entry.packet + ");\n");
|
||||||
|
} else {
|
||||||
|
dump("trace.expectReceive(" + entry.packet + ");\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
function PostInitGlobalActor(aConnection) {}
|
||||||
|
|
||||||
|
PostInitGlobalActor.prototype = {
|
||||||
|
actorPrefix: "postInitGlobal",
|
||||||
|
onPing: function onPing(aRequest) {
|
||||||
|
return { message: "pong" };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
PostInitGlobalActor.prototype.requestTypes = {
|
||||||
|
"ping": PostInitGlobalActor.prototype.onPing,
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerServer.addGlobalActor(PostInitGlobalActor, "postInitGlobalActor");
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
function PostInitTabActor(aConnection) {}
|
||||||
|
|
||||||
|
PostInitTabActor.prototype = {
|
||||||
|
actorPostfix: "postInitTab",
|
||||||
|
onPing: function onPing(aRequest) {
|
||||||
|
return { message: "pong" };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
PostInitTabActor.prototype.requestTypes = {
|
||||||
|
"ping": PostInitTabActor.prototype.onPing,
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerServer.addGlobalActor(PostInitTabActor, "postInitTabActor");
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
function PreInitGlobalActor(aConnection) {}
|
||||||
|
|
||||||
|
PreInitGlobalActor.prototype = {
|
||||||
|
actorPrefix: "preInitGlobal",
|
||||||
|
onPing: function onPing(aRequest) {
|
||||||
|
return { message: "pong" };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
PreInitGlobalActor.prototype.requestTypes = {
|
||||||
|
"ping": PreInitGlobalActor.prototype.onPing,
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerServer.addGlobalActor(PreInitGlobalActor, "preInitGlobalActor");
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
function PreInitTabActor(aConnection) {}
|
||||||
|
|
||||||
|
PreInitTabActor.prototype = {
|
||||||
|
actorPrefix: "preInitTab",
|
||||||
|
onPing: function onPing(aRequest) {
|
||||||
|
return { message: "pong" };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
PreInitTabActor.prototype.requestTypes = {
|
||||||
|
"ping": PreInitTabActor.prototype.onPing,
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerServer.addGlobalActor(PreInitTabActor, "preInitTabActor");
|
|
@ -0,0 +1,15 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
function Actor() {}
|
||||||
|
|
||||||
|
exports.register = function(handle) {
|
||||||
|
handle.addTabActor(Actor, "registeredActor1");
|
||||||
|
handle.addGlobalActor(Actor, "registeredActor1");
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.unregister = function(handle) {
|
||||||
|
handle.removeTabActor(Actor);
|
||||||
|
handle.removeGlobalActor(Actor);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
function Actor() {}
|
||||||
|
|
||||||
|
exports.register = function(handle) {
|
||||||
|
handle.addGlobalActor(Actor, "registeredActor2");
|
||||||
|
handle.addTabActor(Actor, "registeredActor2");
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.unregister = function(handle) {
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
foo = (n) ->
|
||||||
|
return "foo" + i for i in [0...n]
|
||||||
|
|
||||||
|
[first, second, third] = foo(3)
|
||||||
|
|
||||||
|
debugger
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"file": "sourcemapped.js",
|
||||||
|
"sourceRoot": "",
|
||||||
|
"sources": [
|
||||||
|
"sourcemapped.coffee"
|
||||||
|
],
|
||||||
|
"names": [],
|
||||||
|
"mappings": ";AAAA;CAAA,KAAA,yBAAA;CAAA;CAAA,CAAA,CAAA,MAAO;CACL,IAAA,GAAA;AAAA,CAAA,EAAA,MAA0B,qDAA1B;CAAA,EAAe,EAAR,QAAA;CAAP,IADI;CAAN,EAAM;;CAAN,CAGA,CAAyB,IAAA;;CAEzB,UALA;CAAA"
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Generated by CoffeeScript 1.6.1
|
||||||
|
(function() {
|
||||||
|
var first, foo, second, third, _ref;
|
||||||
|
|
||||||
|
foo = function(n) {
|
||||||
|
var i, _i;
|
||||||
|
for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) {
|
||||||
|
return "foo" + i;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_ref = foo(3), first = _ref[0], second = _ref[1], third = _ref[2];
|
||||||
|
|
||||||
|
debugger;
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -0,0 +1,87 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
var gClient;
|
||||||
|
var gActors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The purpose of these tests is to verify that it's possible to add actors
|
||||||
|
* both before and after the DebuggerServer has been initialized, so addons
|
||||||
|
* that add actors don't have to poll the object for its initialization state
|
||||||
|
* in order to add actors after initialization but rather can add actors anytime
|
||||||
|
* regardless of the object's state.
|
||||||
|
*/
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
DebuggerServer.addActors("resource://test/pre_init_global_actors.js");
|
||||||
|
DebuggerServer.addActors("resource://test/pre_init_tab_actors.js");
|
||||||
|
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
|
||||||
|
DebuggerServer.addActors("resource://test/post_init_global_actors.js");
|
||||||
|
DebuggerServer.addActors("resource://test/post_init_tab_actors.js");
|
||||||
|
|
||||||
|
add_test(init);
|
||||||
|
add_test(test_pre_init_global_actor);
|
||||||
|
add_test(test_pre_init_tab_actor);
|
||||||
|
add_test(test_post_init_global_actor);
|
||||||
|
add_test(test_post_init_tab_actor);
|
||||||
|
add_test(close_client);
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function onConnect() {
|
||||||
|
gClient.listTabs(function onListTabs(aResponse) {
|
||||||
|
gActors = aResponse;
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pre_init_global_actor()
|
||||||
|
{
|
||||||
|
gClient.request({ to: gActors.preInitGlobalActor, type: "ping" },
|
||||||
|
function onResponse(aResponse) {
|
||||||
|
do_check_eq(aResponse.message, "pong");
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pre_init_tab_actor()
|
||||||
|
{
|
||||||
|
gClient.request({ to: gActors.preInitTabActor, type: "ping" },
|
||||||
|
function onResponse(aResponse) {
|
||||||
|
do_check_eq(aResponse.message, "pong");
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_post_init_global_actor()
|
||||||
|
{
|
||||||
|
gClient.request({ to: gActors.postInitGlobalActor, type: "ping" },
|
||||||
|
function onResponse(aResponse) {
|
||||||
|
do_check_eq(aResponse.message, "pong");
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_post_init_tab_actor()
|
||||||
|
{
|
||||||
|
gClient.request({ to: gActors.postInitTabActor, type: "ping" },
|
||||||
|
function onResponse(aResponse) {
|
||||||
|
do_check_eq(aResponse.message, "pong");
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close_client() {
|
||||||
|
gClient.close(() => run_next_test());
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
var gClient;
|
||||||
|
var gDebuggee;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = testGlobal("test-1");
|
||||||
|
DebuggerServer.addTestGlobal(gDebuggee);
|
||||||
|
|
||||||
|
let transport = DebuggerServer.connectPipe();
|
||||||
|
gClient = new DebuggerClient(transport);
|
||||||
|
gClient.connect(function(aType, aTraits) {
|
||||||
|
attachTestTab(gClient, "test-1", function(aReply, aTabClient) {
|
||||||
|
test_attach(aReply.threadActor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_attach(aThreadActorID)
|
||||||
|
{
|
||||||
|
gClient.attachThread(aThreadActorID, function(aResponse, aThreadClient) {
|
||||||
|
do_check_eq(aThreadClient.state, "paused");
|
||||||
|
aThreadClient.resume(cleanup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup()
|
||||||
|
{
|
||||||
|
gClient.addListener("closed", function(aEvent) {
|
||||||
|
do_test_finished();
|
||||||
|
});
|
||||||
|
gClient.close();
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test basic black boxing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-black-box");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-black-box", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_black_box();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
|
||||||
|
const SOURCE_URL = "http://example.com/source.js";
|
||||||
|
|
||||||
|
function test_black_box()
|
||||||
|
{
|
||||||
|
gClient.addOneTimeListener("paused", function () {
|
||||||
|
gThreadClient.setBreakpoint({
|
||||||
|
url: SOURCE_URL,
|
||||||
|
line: 2
|
||||||
|
}, function (aResponse) {
|
||||||
|
do_check_true(!aResponse.error, "Should be able to set breakpoint.");
|
||||||
|
gThreadClient.resume(test_black_box_default);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function doStuff(k) { // line 1
|
||||||
|
let arg = 15; // line 2 - Step in here
|
||||||
|
k(arg); // line 3
|
||||||
|
}, // line 4
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
BLACK_BOXED_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function runTest() { // line 1
|
||||||
|
doStuff( // line 2 - Break here
|
||||||
|
function (n) { // line 3 - Step through `doStuff` to here
|
||||||
|
debugger; // line 5
|
||||||
|
} // line 6
|
||||||
|
); // line 7
|
||||||
|
} // line 8
|
||||||
|
+ "\n debugger;", // line 9
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
SOURCE_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_black_box_default() {
|
||||||
|
gThreadClient.getSources(function (aResponse) {
|
||||||
|
do_check_true(!aResponse.error, "Should be able to get sources.");
|
||||||
|
|
||||||
|
let sourceClient = gThreadClient.source(
|
||||||
|
aResponse.sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
|
||||||
|
do_check_true(!sourceClient.isBlackBoxed,
|
||||||
|
"By default the source is not black boxed.");
|
||||||
|
|
||||||
|
// Test that we can step into `doStuff` when we are not black boxed.
|
||||||
|
runTest(
|
||||||
|
function onSteppedLocation(aLocation) {
|
||||||
|
do_check_eq(aLocation.url, BLACK_BOXED_URL,
|
||||||
|
"Should step into `doStuff`.");
|
||||||
|
do_check_eq(aLocation.line, 2,
|
||||||
|
"Should step into `doStuff`.");
|
||||||
|
},
|
||||||
|
function onDebuggerStatementFrames(aFrames) {
|
||||||
|
do_check_true(!aFrames.some(f => f.isBlackBoxed));
|
||||||
|
},
|
||||||
|
test_black_boxing.bind(null, sourceClient)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_black_boxing(aSourceClient) {
|
||||||
|
aSourceClient.blackBox(function (aResponse) {
|
||||||
|
do_check_true(!aResponse.error, "Should not get an error black boxing.");
|
||||||
|
do_check_true(aSourceClient.isBlackBoxed,
|
||||||
|
"The source client should report itself as black boxed correctly.");
|
||||||
|
|
||||||
|
// Test that we step through `doStuff` when we are black boxed and its frame
|
||||||
|
// doesn't show up.
|
||||||
|
runTest(
|
||||||
|
function onSteppedLocation(aLocation) {
|
||||||
|
do_check_eq(aLocation.url, SOURCE_URL,
|
||||||
|
"Should step through `doStuff`.");
|
||||||
|
do_check_eq(aLocation.line, 3,
|
||||||
|
"Should step through `doStuff`.");
|
||||||
|
},
|
||||||
|
function onDebuggerStatementFrames(aFrames) {
|
||||||
|
for (let f of aFrames) {
|
||||||
|
if (f.where.url == BLACK_BOXED_URL) {
|
||||||
|
do_check_true(f.isBlackBoxed, "Should be black boxed");
|
||||||
|
} else {
|
||||||
|
do_check_true(!f.isBlackBoxed, "Should not be black boxed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
test_unblack_boxing.bind(null, aSourceClient)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_unblack_boxing(aSourceClient) {
|
||||||
|
aSourceClient.unblackBox(function (aResponse) {
|
||||||
|
do_check_true(!aResponse.error, "Should not get an error un-black boxing");
|
||||||
|
do_check_true(!aSourceClient.isBlackBoxed, "The source is not black boxed.");
|
||||||
|
|
||||||
|
// Test that we can step into `doStuff` again.
|
||||||
|
runTest(
|
||||||
|
function onSteppedLocation(aLocation) {
|
||||||
|
do_check_eq(aLocation.url, BLACK_BOXED_URL,
|
||||||
|
"Should step into `doStuff`.");
|
||||||
|
do_check_eq(aLocation.line, 2,
|
||||||
|
"Should step into `doStuff`.");
|
||||||
|
},
|
||||||
|
function onDebuggerStatementFrames(aFrames) {
|
||||||
|
do_check_true(!aFrames.some(f => f.isBlackBoxed));
|
||||||
|
},
|
||||||
|
finishClient.bind(null, gClient)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTest(aOnSteppedLocation, aOnDebuggerStatementFrames, aFinishedCallback) {
|
||||||
|
gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
gClient.addOneTimeListener("paused", function () {
|
||||||
|
gClient.addOneTimeListener("paused", function () {
|
||||||
|
getCurrentLocation(function (aLocation) {
|
||||||
|
aOnSteppedLocation(aLocation);
|
||||||
|
gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "debuggerStatement");
|
||||||
|
gThreadClient.getFrames(0, 100, function ({frames}) {
|
||||||
|
aOnDebuggerStatementFrames(frames);
|
||||||
|
// We hit the breakpoint once more on the way out
|
||||||
|
gClient.addOneTimeListener("paused", function () {
|
||||||
|
gThreadClient.resume(aFinishedCallback);
|
||||||
|
});
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
gThreadClient.stepIn();
|
||||||
|
});
|
||||||
|
gThreadClient.stepIn();
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.runTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLocation(aCallback) {
|
||||||
|
gThreadClient.getFrames(0, 1, function ({frames, error}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
let [{where}] = frames;
|
||||||
|
aCallback(where);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that we don't hit breakpoints in black boxed sources, and that when we
|
||||||
|
* unblack box the source again, the breakpoint hasn't disappeared and we will
|
||||||
|
* hit it again.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-black-box");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-black-box", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_black_box();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
|
||||||
|
const SOURCE_URL = "http://example.com/source.js";
|
||||||
|
|
||||||
|
function test_black_box()
|
||||||
|
{
|
||||||
|
gClient.addOneTimeListener("paused", function () {
|
||||||
|
gThreadClient.setBreakpoint({
|
||||||
|
url: BLACK_BOXED_URL,
|
||||||
|
line: 2
|
||||||
|
}, function (aResponse) {
|
||||||
|
do_check_true(!aResponse.error, "Should be able to set breakpoint.");
|
||||||
|
gThreadClient.resume(test_black_box_breakpoint);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function doStuff(k) { // line 1
|
||||||
|
let arg = 15; // line 2 - Break here
|
||||||
|
k(arg); // line 3
|
||||||
|
}, // line 4
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
BLACK_BOXED_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function runTest() { // line 1
|
||||||
|
doStuff( // line 2
|
||||||
|
function (n) { // line 3
|
||||||
|
debugger; // line 5
|
||||||
|
} // line 6
|
||||||
|
); // line 7
|
||||||
|
} // line 8
|
||||||
|
+ "\n debugger;", // line 9
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
SOURCE_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_black_box_breakpoint() {
|
||||||
|
gThreadClient.getSources(function ({error, sources}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
|
||||||
|
sourceClient.blackBox(function ({error}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
|
||||||
|
gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "debuggerStatement",
|
||||||
|
"We should pass over the breakpoint since the source is black boxed.");
|
||||||
|
gThreadClient.resume(test_unblack_box_breakpoint.bind(null, sourceClient));
|
||||||
|
});
|
||||||
|
gDebuggee.runTest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_unblack_box_breakpoint(aSourceClient) {
|
||||||
|
aSourceClient.unblackBox(function ({error}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint",
|
||||||
|
"We should hit the breakpoint again");
|
||||||
|
|
||||||
|
// We will hit the debugger statement on resume, so do this nastiness to skip over it.
|
||||||
|
gClient.addOneTimeListener(
|
||||||
|
"paused",
|
||||||
|
gThreadClient.resume.bind(
|
||||||
|
gThreadClient,
|
||||||
|
finishClient.bind(null, gClient)));
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
gDebuggee.runTest();
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that we don't stop at debugger statements inside black boxed sources.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-black-box");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-black-box", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_black_box();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
|
||||||
|
const SOURCE_URL = "http://example.com/source.js";
|
||||||
|
|
||||||
|
function test_black_box()
|
||||||
|
{
|
||||||
|
gClient.addOneTimeListener("paused", function () {
|
||||||
|
gThreadClient.setBreakpoint({
|
||||||
|
url: SOURCE_URL,
|
||||||
|
line: 4
|
||||||
|
}, function ({error}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
gThreadClient.resume(test_black_box_dbg_statement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function doStuff(k) { // line 1
|
||||||
|
debugger; // line 2 - Break here
|
||||||
|
k(100); // line 3
|
||||||
|
}, // line 4
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
BLACK_BOXED_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function runTest() { // line 1
|
||||||
|
doStuff( // line 2
|
||||||
|
function (n) { // line 3
|
||||||
|
Math.abs(n); // line 4 - Break here
|
||||||
|
} // line 5
|
||||||
|
); // line 6
|
||||||
|
} // line 7
|
||||||
|
+ "\n debugger;", // line 8
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
SOURCE_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_black_box_dbg_statement() {
|
||||||
|
gThreadClient.getSources(function ({error, sources}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
|
||||||
|
|
||||||
|
sourceClient.blackBox(function ({error}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
|
||||||
|
gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint",
|
||||||
|
"We should pass over the debugger statement.");
|
||||||
|
gThreadClient.resume(test_unblack_box_dbg_statement.bind(null, sourceClient));
|
||||||
|
});
|
||||||
|
gDebuggee.runTest();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_unblack_box_dbg_statement(aSourceClient) {
|
||||||
|
aSourceClient.unblackBox(function ({error}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
|
||||||
|
gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "debuggerStatement",
|
||||||
|
"We should stop at the debugger statement again");
|
||||||
|
|
||||||
|
// We will hit the breakpoint on resume, so do this nastiness to skip over it.
|
||||||
|
gClient.addOneTimeListener(
|
||||||
|
"paused",
|
||||||
|
gThreadClient.resume.bind(
|
||||||
|
gThreadClient,
|
||||||
|
finishClient.bind(null, gClient)));
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
gDebuggee.runTest();
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test behavior of blackboxing sources we are currently paused in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-black-box");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-black-box", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_black_box();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
|
||||||
|
const SOURCE_URL = "http://example.com/source.js";
|
||||||
|
|
||||||
|
function test_black_box()
|
||||||
|
{
|
||||||
|
gClient.addOneTimeListener("paused", function () {
|
||||||
|
gThreadClient.setBreakpoint({
|
||||||
|
url: BLACK_BOXED_URL,
|
||||||
|
line: 2
|
||||||
|
}, function (aResponse) {
|
||||||
|
do_check_true(!aResponse.error, "Should be able to set breakpoint.");
|
||||||
|
test_black_box_paused();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function doStuff(k) { // line 1
|
||||||
|
debugger; // line 2
|
||||||
|
k(100); // line 3
|
||||||
|
}, // line 4
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
BLACK_BOXED_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function runTest() { // line 1
|
||||||
|
doStuff( // line 2
|
||||||
|
function (n) { // line 3
|
||||||
|
n; // line 4
|
||||||
|
} // line 5
|
||||||
|
); // line 6
|
||||||
|
} // line 7
|
||||||
|
+ "\n runTest();", // line 8
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
SOURCE_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_black_box_paused() {
|
||||||
|
gThreadClient.getSources(function ({error, sources}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
|
||||||
|
|
||||||
|
sourceClient.blackBox(function ({error, pausedInSource}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
do_check_true(pausedInSource, "We should be notified that we are currently paused in this source");
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test exceptions inside black boxed sources.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-black-box");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-black-box", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_black_box();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
|
||||||
|
const SOURCE_URL = "http://example.com/source.js";
|
||||||
|
|
||||||
|
function test_black_box()
|
||||||
|
{
|
||||||
|
gClient.addOneTimeListener("paused", test_black_box_exception);
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function doStuff(k) { // line 1
|
||||||
|
throw new Error("wu tang clan ain't nuthin' ta fuck wit"); // line 2
|
||||||
|
k(100); // line 3
|
||||||
|
}, // line 4
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
BLACK_BOXED_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
Components.utils.evalInSandbox(
|
||||||
|
"" + function runTest() { // line 1
|
||||||
|
doStuff( // line 2
|
||||||
|
function (n) { // line 3
|
||||||
|
debugger; // line 4
|
||||||
|
} // line 5
|
||||||
|
); // line 6
|
||||||
|
} // line 7
|
||||||
|
+ "\ndebugger;\n" // line 8
|
||||||
|
+ "runTest()", // line 9
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
SOURCE_URL,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_black_box_exception() {
|
||||||
|
gThreadClient.getSources(function ({error, sources}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
|
||||||
|
|
||||||
|
sourceClient.blackBox(function ({error}) {
|
||||||
|
do_check_true(!error, "Should not get an error: " + error);
|
||||||
|
gThreadClient.pauseOnExceptions(true);
|
||||||
|
|
||||||
|
gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_neq(aPacket.frame.where.url, BLACK_BOXED_URL,
|
||||||
|
"We shouldn't pause while in the black boxed source.");
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check basic breakpoint functionality.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_simple_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_simple_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-01.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 3};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"debugger;\n" + // line0 + 1
|
||||||
|
"var a = 1;\n" + // line0 + 2
|
||||||
|
"var b = 2;\n"); // line0 + 3
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting breakpoints when the debuggee is running works.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_breakpoint_running();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_breakpoint_running()
|
||||||
|
{
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"var a = 1;\n" + // line0 + 1
|
||||||
|
"var b = 2;\n"); // line0 + 2
|
||||||
|
|
||||||
|
let path = getFilePath('test_breakpoint-02.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 2};
|
||||||
|
|
||||||
|
// Setting the breakpoint later should interrupt the debuggee.
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "interrupted");
|
||||||
|
});
|
||||||
|
|
||||||
|
gThreadClient.setBreakpoint(location, function(aResponse) {
|
||||||
|
// Eval scripts don't stick around long enough for the breakpoint to be set,
|
||||||
|
// so just make sure we got the expected response from the actor.
|
||||||
|
do_check_neq(aResponse.error, "noScript");
|
||||||
|
|
||||||
|
do_execute_soon(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting a breakpoint in a line without code will skip forward.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient,
|
||||||
|
"test-stack",
|
||||||
|
function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_skip_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_skip_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-03.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 3};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// Check that the breakpoint has properly skipped forward one line.
|
||||||
|
do_check_eq(aResponse.actualLocation.url, location.url);
|
||||||
|
do_check_eq(aResponse.actualLocation.line, location.line + 1);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line + 1);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"debugger;\n" + // line0 + 1
|
||||||
|
"var a = 1;\n" + // line0 + 2
|
||||||
|
"// A comment.\n" + // line0 + 3
|
||||||
|
"var b = 2;\n"); // line0 + 4
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting a breakpoint in a line in a child script works.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_child_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_child_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-04.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 3};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// actualLocation is not returned when breakpoints don't skip forward.
|
||||||
|
do_check_eq(aResponse.actualLocation, undefined);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"function foo() {\n" + // line0 + 1
|
||||||
|
" this.a = 1;\n" + // line0 + 2
|
||||||
|
" this.b = 2;\n" + // line0 + 3
|
||||||
|
"}\n" + // line0 + 4
|
||||||
|
"debugger;\n" + // line0 + 5
|
||||||
|
"foo();\n"); // line0 + 6
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting a breakpoint in a line without code in a child script
|
||||||
|
* will skip forward.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_child_skip_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_child_skip_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-05.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 3};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// Check that the breakpoint has properly skipped forward one line.
|
||||||
|
do_check_eq(aResponse.actualLocation.url, location.url);
|
||||||
|
do_check_eq(aResponse.actualLocation.line, location.line + 1);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line + 1);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"function foo() {\n" + // line0 + 1
|
||||||
|
" this.a = 1;\n" + // line0 + 2
|
||||||
|
" // A comment.\n" + // line0 + 3
|
||||||
|
" this.b = 2;\n" + // line0 + 4
|
||||||
|
"}\n" + // line0 + 5
|
||||||
|
"debugger;\n" + // line0 + 6
|
||||||
|
"foo();\n"); // line0 + 7
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting a breakpoint in a line without code in a deeply-nested
|
||||||
|
* child script will skip forward.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_nested_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_nested_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-06.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 5};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// Check that the breakpoint has properly skipped forward one line.
|
||||||
|
do_check_eq(aResponse.actualLocation.url, location.url);
|
||||||
|
do_check_eq(aResponse.actualLocation.line, location.line + 1);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line + 1);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"function foo() {\n" + // line0 + 1
|
||||||
|
" function bar() {\n" + // line0 + 2
|
||||||
|
" function baz() {\n" + // line0 + 3
|
||||||
|
" this.a = 1;\n" + // line0 + 4
|
||||||
|
" // A comment.\n" + // line0 + 5
|
||||||
|
" this.b = 2;\n" + // line0 + 6
|
||||||
|
" }\n" + // line0 + 7
|
||||||
|
" baz();\n" + // line0 + 8
|
||||||
|
" }\n" + // line0 + 9
|
||||||
|
" bar();\n" + // line0 + 10
|
||||||
|
"}\n" + // line0 + 11
|
||||||
|
"debugger;\n" + // line0 + 12
|
||||||
|
"foo();\n"); // line0 + 13
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting a breakpoint in a line without code in the second child
|
||||||
|
* script will skip forward.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_second_child_skip_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_second_child_skip_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-07.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 6};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// Check that the breakpoint has properly skipped forward one line.
|
||||||
|
do_check_eq(aResponse.actualLocation.url, location.url);
|
||||||
|
do_check_eq(aResponse.actualLocation.line, location.line + 1);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line + 1);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"function foo() {\n" + // line0 + 1
|
||||||
|
" bar();\n" + // line0 + 2
|
||||||
|
"}\n" + // line0 + 3
|
||||||
|
"function bar() {\n" + // line0 + 4
|
||||||
|
" this.a = 1;\n" + // line0 + 5
|
||||||
|
" // A comment.\n" + // line0 + 6
|
||||||
|
" this.b = 2;\n" + // line0 + 7
|
||||||
|
"}\n" + // line0 + 8
|
||||||
|
"debugger;\n" + // line0 + 9
|
||||||
|
"foo();\n"); // line0 + 10
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting a breakpoint in a line without code in a child script
|
||||||
|
* will skip forward, in a file with two scripts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_child_skip_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_child_skip_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-08.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 3};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// Check that the breakpoint has properly skipped forward one line.
|
||||||
|
do_check_eq(aResponse.actualLocation.url, location.url);
|
||||||
|
do_check_eq(aResponse.actualLocation.line, location.line + 1);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line + 1);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"function foo() {\n" + // line0 + 1
|
||||||
|
" this.a = 1;\n" + // line0 + 2
|
||||||
|
" // A comment.\n" + // line0 + 3
|
||||||
|
" this.b = 2;\n" + // line0 + 4
|
||||||
|
"}\n"); // line0 + 5
|
||||||
|
gDebuggee.eval("var line1 = Error().lineNumber;\n" +
|
||||||
|
"debugger;\n" + // line1 + 1
|
||||||
|
"foo();\n"); // line1 + 2
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that removing a breakpoint works.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_remove_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_remove_breakpoint()
|
||||||
|
{
|
||||||
|
let done = false;
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-09.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 2};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, path);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
done = true;
|
||||||
|
gThreadClient.addOneTimeListener("paused",
|
||||||
|
function (aEvent, aPacket) {
|
||||||
|
// The breakpoint should not be hit again.
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
do_check_true(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"function foo(stop) {\n" + // line0 + 1
|
||||||
|
" this.a = 1;\n" + // line0 + 2
|
||||||
|
" if (stop) return;\n" + // line0 + 3
|
||||||
|
" delete this.a;\n" + // line0 + 4
|
||||||
|
" foo(true);\n" + // line0 + 5
|
||||||
|
"}\n" + // line0 + 6
|
||||||
|
"debugger;\n" + // line1 + 7
|
||||||
|
"foo();\n"); // line1 + 8
|
||||||
|
if (!done) {
|
||||||
|
do_check_true(false);
|
||||||
|
}
|
||||||
|
finishClient(gClient);
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that setting a breakpoint in a line with multiple entry points
|
||||||
|
* triggers no matter which entry point we reach.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_child_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_child_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-10.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 3};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// actualLocation is not returned when breakpoints don't skip forward.
|
||||||
|
do_check_eq(aResponse.actualLocation, undefined);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.i, 0);
|
||||||
|
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.i, 1);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Continue until the breakpoint is hit again.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"debugger;\n" + // line0 + 1
|
||||||
|
"var a, i = 0;\n" + // line0 + 2
|
||||||
|
"for (i = 1; i <= 2; i++) {\n" + // line0 + 3
|
||||||
|
" a = i;\n" + // line0 + 4
|
||||||
|
"}\n"); // line0 + 5
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that setting a breakpoint in a line with bytecodes in multiple
|
||||||
|
* scripts, sets the breakpoint in all of them (bug 793214).
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_child_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_child_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let path = getFilePath('test_breakpoint-11.js');
|
||||||
|
let location = { url: path, line: gDebuggee.line0 + 2};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// actualLocation is not returned when breakpoints don't skip forward.
|
||||||
|
do_check_eq(aResponse.actualLocation, undefined);
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, undefined);
|
||||||
|
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a.b, 1);
|
||||||
|
do_check_eq(gDebuggee.res, undefined);
|
||||||
|
|
||||||
|
// Remove the breakpoint.
|
||||||
|
bpClient.remove(function (aResponse) {
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Continue until the breakpoint is hit again.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"debugger;\n" + // line0 + 1
|
||||||
|
"var a = { b: 1, f: function() { return 2; } };\n" + // line0+2
|
||||||
|
"var res = a.f();\n"); // line0 + 3
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that setting a breakpoint twice in a line without bytecodes works
|
||||||
|
* as expected.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NUM_BREAKPOINTS = 10;
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
var gPath = getFilePath('test_breakpoint-12.js');
|
||||||
|
var gBpActor;
|
||||||
|
var gCount = 1;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_child_skip_breakpoint();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_child_skip_breakpoint()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
let location = { url: gPath, line: gDebuggee.line0 + 3};
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// Check that the breakpoint has properly skipped forward one line.
|
||||||
|
do_check_eq(aResponse.actualLocation.url, location.url);
|
||||||
|
do_check_eq(aResponse.actualLocation.line, location.line + 1);
|
||||||
|
gBpActor = aResponse.actor;
|
||||||
|
|
||||||
|
// Set more breakpoints at the same location.
|
||||||
|
set_breakpoints(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"function foo() {\n" + // line0 + 1
|
||||||
|
" this.a = 1;\n" + // line0 + 2
|
||||||
|
" // A comment.\n" + // line0 + 3
|
||||||
|
" this.b = 2;\n" + // line0 + 4
|
||||||
|
"}\n" + // line0 + 5
|
||||||
|
"debugger;\n" + // line0 + 6
|
||||||
|
"foo();\n"); // line0 + 7
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set many breakpoints at the same location.
|
||||||
|
function set_breakpoints(location) {
|
||||||
|
do_check_neq(gCount, NUM_BREAKPOINTS);
|
||||||
|
gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
|
||||||
|
// Check that the breakpoint has properly skipped forward one line.
|
||||||
|
do_check_eq(aResponse.actualLocation.url, location.url);
|
||||||
|
do_check_eq(aResponse.actualLocation.line, location.line + 1);
|
||||||
|
// Check that the same breakpoint actor was returned.
|
||||||
|
do_check_eq(aResponse.actor, gBpActor);
|
||||||
|
|
||||||
|
if (++gCount < NUM_BREAKPOINTS) {
|
||||||
|
set_breakpoints(location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After setting all the breakpoints, check that only one has effectively
|
||||||
|
// remained.
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// Check the return value.
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.frame.where.url, gPath);
|
||||||
|
do_check_eq(aPacket.frame.where.line, location.line + 1);
|
||||||
|
do_check_eq(aPacket.why.type, "breakpoint");
|
||||||
|
do_check_eq(aPacket.why.actors[0], bpClient.actor);
|
||||||
|
// Check that the breakpoint worked.
|
||||||
|
do_check_eq(gDebuggee.a, 1);
|
||||||
|
do_check_eq(gDebuggee.b, undefined);
|
||||||
|
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
// We don't expect any more pauses after the breakpoint was hit once.
|
||||||
|
do_check_true(false);
|
||||||
|
});
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
// Give any remaining breakpoints a chance to trigger.
|
||||||
|
do_timeout(1000, finishClient.bind(null, gClient));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
// Continue until the breakpoint is hit.
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
// Note: Removing this test will regress bug 754251. See comment above
|
||||||
|
// ThreadActor._breakpointStore.
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||||
|
addDebuggerToGlobal(this);
|
||||||
|
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||||
|
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
||||||
|
loader.loadSubScript("resource://gre/modules/devtools/server/actors/script.js");
|
||||||
|
|
||||||
|
let instance1 = new ThreadActor();
|
||||||
|
let instance2 = new ThreadActor();
|
||||||
|
do_check_eq(instance1._breakpointStore, ThreadActor._breakpointStore);
|
||||||
|
do_check_eq(instance2._breakpointStore, ThreadActor._breakpointStore);
|
||||||
|
do_check_eq(instance1._breakpointStore, instance2._breakpointStore);
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
|
|
||||||
|
var gClient;
|
||||||
|
var gDebuggee;
|
||||||
|
|
||||||
|
const xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = testGlobal("test-1");
|
||||||
|
DebuggerServer.addTestGlobal(gDebuggee);
|
||||||
|
|
||||||
|
let transport = DebuggerServer.connectPipe();
|
||||||
|
gClient = new DebuggerClient(transport);
|
||||||
|
gClient.addListener("connected", function(aEvent, aType, aTraits) {
|
||||||
|
gClient.listTabs((aResponse) => {
|
||||||
|
do_check_true('tabs' in aResponse);
|
||||||
|
for (let tab of aResponse.tabs) {
|
||||||
|
if (tab.title == "test-1") {
|
||||||
|
test_attach_tab(tab.actor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do_check_true(false); // We should have found our tab in the list.
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gClient.connect();
|
||||||
|
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to |aTabActor|, and check the response.
|
||||||
|
function test_attach_tab(aTabActor)
|
||||||
|
{
|
||||||
|
gClient.request({ to: aTabActor, type: "attach" }, function(aResponse) {
|
||||||
|
do_check_false("error" in aResponse);
|
||||||
|
do_check_eq(aResponse.from, aTabActor);
|
||||||
|
do_check_eq(aResponse.type, "tabAttached");
|
||||||
|
do_check_true(typeof aResponse.threadActor === "string");
|
||||||
|
|
||||||
|
test_attach_thread(aResponse.threadActor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to |aThreadActor|, check the response, and resume it.
|
||||||
|
function test_attach_thread(aThreadActor)
|
||||||
|
{
|
||||||
|
gClient.request({ to: aThreadActor, type: "attach" }, function(aResponse) {
|
||||||
|
do_check_false("error" in aResponse);
|
||||||
|
do_check_eq(aResponse.from, aThreadActor);
|
||||||
|
do_check_eq(aResponse.type, "paused");
|
||||||
|
do_check_true("why" in aResponse);
|
||||||
|
do_check_eq(aResponse.why.type, "attached");
|
||||||
|
|
||||||
|
test_resume_thread(aThreadActor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume |aThreadActor|, and see that it stops at the 'debugger'
|
||||||
|
// statement.
|
||||||
|
function test_resume_thread(aThreadActor)
|
||||||
|
{
|
||||||
|
// Allow the client to resume execution.
|
||||||
|
gClient.request({ to: aThreadActor, type: "resume" }, function (aResponse) {
|
||||||
|
do_check_false("error" in aResponse);
|
||||||
|
do_check_eq(aResponse.from, aThreadActor);
|
||||||
|
do_check_eq(aResponse.type, "resumed");
|
||||||
|
|
||||||
|
do_check_eq(xpcInspector.eventLoopNestLevel, 0);
|
||||||
|
|
||||||
|
// Now that we know we're resumed, we can make the debuggee do something.
|
||||||
|
Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee);
|
||||||
|
// Now make sure that we've run the code after the debugger statement...
|
||||||
|
do_check_true(gDebuggee.b);
|
||||||
|
});
|
||||||
|
|
||||||
|
gClient.addListener("paused", function(aName, aPacket) {
|
||||||
|
do_check_eq(aName, "paused");
|
||||||
|
do_check_false("error" in aPacket);
|
||||||
|
do_check_eq(aPacket.from, aThreadActor);
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_true("actor" in aPacket);
|
||||||
|
do_check_true("why" in aPacket)
|
||||||
|
do_check_eq(aPacket.why.type, "debuggerStatement");
|
||||||
|
|
||||||
|
// Reach around the protocol to check that the debuggee is in the state
|
||||||
|
// we expect.
|
||||||
|
do_check_true(gDebuggee.a);
|
||||||
|
do_check_false(gDebuggee.b);
|
||||||
|
|
||||||
|
do_check_eq(xpcInspector.eventLoopNestLevel, 1);
|
||||||
|
|
||||||
|
// Let the debuggee continue execution.
|
||||||
|
gClient.request({ to: aThreadActor, type: "resume" }, cleanup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup()
|
||||||
|
{
|
||||||
|
gClient.addListener("closed", function(aEvent, aResult) {
|
||||||
|
do_test_finished();
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
|
||||||
|
do_check_eq(xpcInspector.eventLoopNestLevel, 0);
|
||||||
|
} catch(e) {
|
||||||
|
dump(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
gClient.close();
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
|
|
||||||
|
var gClient;
|
||||||
|
var gDebuggee;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = testGlobal("test-1");
|
||||||
|
DebuggerServer.addTestGlobal(gDebuggee);
|
||||||
|
|
||||||
|
let transport = DebuggerServer.connectPipe();
|
||||||
|
gClient = new DebuggerClient(transport);
|
||||||
|
gClient.connect(function(aType, aTraits) {
|
||||||
|
attachTestTab(gClient, "test-1", function(aReply, aTabClient) {
|
||||||
|
test_threadAttach(aReply.threadActor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_threadAttach(aThreadActorID)
|
||||||
|
{
|
||||||
|
do_print("Trying to attach to thread " + aThreadActorID);
|
||||||
|
gClient.attachThread(aThreadActorID, function(aResponse, aThreadClient) {
|
||||||
|
do_check_eq(aThreadClient.state, "paused");
|
||||||
|
do_check_eq(aThreadClient.actor, aThreadActorID);
|
||||||
|
aThreadClient.resume(function() {
|
||||||
|
do_check_eq(aThreadClient.state, "attached");
|
||||||
|
test_debugger_statement(aThreadClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_debugger_statement(aThreadClient)
|
||||||
|
{
|
||||||
|
aThreadClient.addListener("paused", function(aEvent, aPacket) {
|
||||||
|
do_check_eq(aThreadClient.state, "paused");
|
||||||
|
// Reach around the protocol to check that the debuggee is in the state
|
||||||
|
// we expect.
|
||||||
|
do_check_true(gDebuggee.a);
|
||||||
|
do_check_false(gDebuggee.b);
|
||||||
|
|
||||||
|
let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
|
||||||
|
do_check_eq(xpcInspector.eventLoopNestLevel, 1);
|
||||||
|
|
||||||
|
aThreadClient.resume(cleanup);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee);
|
||||||
|
|
||||||
|
// Now make sure that we've run the code after the debugger statement...
|
||||||
|
do_check_true(gDebuggee.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup()
|
||||||
|
{
|
||||||
|
gClient.addListener("closed", function(aEvent) {
|
||||||
|
do_test_finished();
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
|
||||||
|
do_check_eq(xpcInspector.eventLoopNestLevel, 0);
|
||||||
|
} catch(e) {
|
||||||
|
dump(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
gClient.close();
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
// Should get an exception if we try to interact with DebuggerServer
|
||||||
|
// before we initialize it...
|
||||||
|
check_except(function() {
|
||||||
|
DebuggerServer.openListener(2929);
|
||||||
|
});
|
||||||
|
check_except(DebuggerServer.closeListener);
|
||||||
|
check_except(DebuggerServer.connectPipe);
|
||||||
|
|
||||||
|
// Allow incoming connections.
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
|
||||||
|
// These should still fail because we haven't added a createRootActor
|
||||||
|
// implementation yet.
|
||||||
|
check_except(function() {
|
||||||
|
DebuggerServer.openListener(2929);
|
||||||
|
});
|
||||||
|
check_except(DebuggerServer.closeListener);
|
||||||
|
check_except(DebuggerServer.connectPipe);
|
||||||
|
|
||||||
|
DebuggerServer.addActors("resource://test/testactors.js");
|
||||||
|
|
||||||
|
// Now they should work.
|
||||||
|
DebuggerServer.openListener(2929);
|
||||||
|
DebuggerServer.closeListener();
|
||||||
|
|
||||||
|
// Make sure we got the test's root actor all set up.
|
||||||
|
let client1 = DebuggerServer.connectPipe();
|
||||||
|
client1.hooks = {
|
||||||
|
onPacket: function(aPacket1) {
|
||||||
|
do_check_eq(aPacket1.from, "root");
|
||||||
|
do_check_eq(aPacket1.applicationType, "xpcshell-tests");
|
||||||
|
|
||||||
|
// Spin up a second connection, make sure it has its own root
|
||||||
|
// actor.
|
||||||
|
let client2 = DebuggerServer.connectPipe();
|
||||||
|
client2.hooks = {
|
||||||
|
onPacket: function(aPacket2) {
|
||||||
|
do_check_eq(aPacket2.from, "root");
|
||||||
|
do_check_neq(aPacket1.testConnectionPrefix,
|
||||||
|
aPacket2.testConnectionPrefix);
|
||||||
|
client2.close();
|
||||||
|
},
|
||||||
|
onClosed: function(aResult) {
|
||||||
|
client1.close();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
client2.ready();
|
||||||
|
},
|
||||||
|
|
||||||
|
onClosed: function(aResult) {
|
||||||
|
do_test_finished();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
client1.ready();
|
||||||
|
do_test_pending();
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
|
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
|
||||||
|
|
||||||
|
var { safeErrorString } = DevToolsUtils;
|
||||||
|
|
||||||
|
let port = 2929;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
do_print("Starting test at " + new Date().toTimeString());
|
||||||
|
initTestDebuggerServer();
|
||||||
|
|
||||||
|
add_test(test_socket_conn);
|
||||||
|
add_test(test_socket_shutdown);
|
||||||
|
add_test(test_pipe_conn);
|
||||||
|
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
function really_long() {
|
||||||
|
let ret = "0123456789";
|
||||||
|
for (let i = 0; i < 18; i++) {
|
||||||
|
ret += ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_socket_conn()
|
||||||
|
{
|
||||||
|
do_check_eq(DebuggerServer._socketConnections, 0);
|
||||||
|
try_open_listener();
|
||||||
|
do_print("Debugger server port is " + port);
|
||||||
|
do_check_eq(DebuggerServer._socketConnections, 1);
|
||||||
|
// Make sure opening the listener twice does nothing.
|
||||||
|
do_check_true(DebuggerServer.openListener(port));
|
||||||
|
do_check_eq(DebuggerServer._socketConnections, 1);
|
||||||
|
|
||||||
|
do_print("Starting long and unicode tests at " + new Date().toTimeString());
|
||||||
|
let unicodeString = "(╯°□°)╯︵ ┻━┻";
|
||||||
|
let transport = debuggerSocketConnect("127.0.0.1", port);
|
||||||
|
transport.hooks = {
|
||||||
|
onPacket: function(aPacket) {
|
||||||
|
this.onPacket = function(aPacket) {
|
||||||
|
do_check_eq(aPacket.unicode, unicodeString);
|
||||||
|
transport.close();
|
||||||
|
}
|
||||||
|
// Verify that things work correctly when bigger than the output
|
||||||
|
// transport buffers and when transporting unicode...
|
||||||
|
transport.send({to: "root",
|
||||||
|
type: "echo",
|
||||||
|
reallylong: really_long(),
|
||||||
|
unicode: unicodeString});
|
||||||
|
do_check_eq(aPacket.from, "root");
|
||||||
|
},
|
||||||
|
onClosed: function(aStatus) {
|
||||||
|
run_next_test();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
transport.ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_socket_shutdown()
|
||||||
|
{
|
||||||
|
do_check_eq(DebuggerServer._socketConnections, 1);
|
||||||
|
do_check_true(DebuggerServer.closeListener());
|
||||||
|
do_check_eq(DebuggerServer._socketConnections, 0);
|
||||||
|
// Make sure closing the listener twice does nothing.
|
||||||
|
do_check_false(DebuggerServer.closeListener());
|
||||||
|
do_check_eq(DebuggerServer._socketConnections, 0);
|
||||||
|
|
||||||
|
do_print("Connecting to a server socket at " + new Date().toTimeString());
|
||||||
|
let transport = debuggerSocketConnect("127.0.0.1", port);
|
||||||
|
transport.hooks = {
|
||||||
|
onPacket: function(aPacket) {
|
||||||
|
// Shouldn't reach this, should never connect.
|
||||||
|
do_check_true(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClosed: function(aStatus) {
|
||||||
|
do_print("test_socket_shutdown onClosed called at " + new Date().toTimeString());
|
||||||
|
do_check_eq(aStatus, Cr.NS_ERROR_CONNECTION_REFUSED);
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hack to get more logging for bug 775924.
|
||||||
|
transport.onDataAvailable = makeInfallible(function DT_onDataAvailable(aRequest, aContext,
|
||||||
|
aStream, aOffset, aCount) {
|
||||||
|
do_print("onDataAvailable. offset: "+aOffset+", count: "+aCount);
|
||||||
|
let buf = NetUtil.readInputStreamToString(aStream, aStream.available());
|
||||||
|
transport._incoming += buf;
|
||||||
|
do_print("Read form stream("+buf.length+"): "+buf);
|
||||||
|
while (transport._processIncoming()) {
|
||||||
|
do_print("Look ma, I'm looping!");
|
||||||
|
};
|
||||||
|
}, "DebuggerTransport.prototype.onDataAvailable");
|
||||||
|
|
||||||
|
do_print("Initializing input stream at " + new Date().toTimeString());
|
||||||
|
transport.ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pipe_conn()
|
||||||
|
{
|
||||||
|
let transport = DebuggerServer.connectPipe();
|
||||||
|
transport.hooks = {
|
||||||
|
onPacket: function(aPacket) {
|
||||||
|
do_check_eq(aPacket.from, "root");
|
||||||
|
transport.close();
|
||||||
|
},
|
||||||
|
onClosed: function(aStatus) {
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
transport.ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
function try_open_listener()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
do_check_true(DebuggerServer.openListener(port));
|
||||||
|
} catch (e) {
|
||||||
|
// In case the port is unavailable, pick a random one between 2000 and 65000.
|
||||||
|
port = Math.floor(Math.random() * (65000 - 2000 + 1)) + 2000;
|
||||||
|
try_open_listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied verbatim from dbg-transport.js.
|
||||||
|
// Hack to get more logging for bug 775924.
|
||||||
|
function makeInfallible(aHandler, aName) {
|
||||||
|
if (!aName)
|
||||||
|
aName = aHandler.name;
|
||||||
|
|
||||||
|
return function (/* arguments */) {
|
||||||
|
try {
|
||||||
|
return aHandler.apply(this, arguments);
|
||||||
|
} catch (ex) {
|
||||||
|
let msg = "Handler function ";
|
||||||
|
if (aName) {
|
||||||
|
msg += aName + " ";
|
||||||
|
}
|
||||||
|
msg += "threw an exception: " + safeErrorString(ex);
|
||||||
|
if (ex.stack) {
|
||||||
|
msg += "\nCall stack:\n" + ex.stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
do_print(msg + "\n");
|
||||||
|
|
||||||
|
if (Cu.reportError) {
|
||||||
|
/*
|
||||||
|
* Note that the xpcshell test harness registers an observer for
|
||||||
|
* console messages, so when we're running tests, this will cause
|
||||||
|
* the test to quit.
|
||||||
|
*/
|
||||||
|
Cu.reportError(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check basic eval resume/re-pause
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_simple_eval();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_simple_eval()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let arg1Actor = aPacket.frame.arguments[0].actor;
|
||||||
|
gThreadClient.eval(null, "({ obj: true })", function(aResponse) {
|
||||||
|
do_check_eq(aResponse.type, "resumed");
|
||||||
|
// Expect a pause notification immediately.
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
// Check the return value...
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "clientEvaluated");
|
||||||
|
do_check_eq(aPacket.why.frameFinished.return.type, "object");
|
||||||
|
do_check_eq(aPacket.why.frameFinished.return.class, "Object");
|
||||||
|
|
||||||
|
// Make sure the previous pause lifetime was correctly dropped.
|
||||||
|
gClient.request({ to: arg1Actor, type: "bogusRequest" }, function(aResponse) {
|
||||||
|
do_check_eq(aResponse.error, "noSuchActor");
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(arg1) { debugger; };
|
||||||
|
stopMe({obj: true});
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check eval resume/re-pause with a throw.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_throw_eval();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_throw_eval()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
gThreadClient.eval(null, "throw 'failure'", function(aResponse) {
|
||||||
|
do_check_eq(aResponse.type, "resumed");
|
||||||
|
// Expect a pause notification immediately.
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
// Check the return value...
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "clientEvaluated");
|
||||||
|
do_check_eq(aPacket.why.frameFinished.throw, "failure");
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(arg1) { debugger; };
|
||||||
|
stopMe({obj: true});
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check syntax errors in an eval.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_syntax_error_eval();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_syntax_error_eval()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
gThreadClient.eval(null, "%$@!@#", function(aResponse) {
|
||||||
|
do_check_eq(aResponse.type, "resumed");
|
||||||
|
// Expect a pause notification immediately.
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
// Check the return value...
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "clientEvaluated");
|
||||||
|
do_check_eq(aPacket.why.frameFinished.throw.type, "object");
|
||||||
|
do_check_eq(aPacket.why.frameFinished.throw.class, "Error");
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(arg1) { debugger; };
|
||||||
|
stopMe({obj: true});
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check evals against different frames.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_syntax_error_eval();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_syntax_error_eval()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
|
||||||
|
gThreadClient.getFrames(0, 2, function(aResponse) {
|
||||||
|
let frame0 = aResponse.frames[0];
|
||||||
|
let frame1 = aResponse.frames[1];
|
||||||
|
|
||||||
|
// Eval against the top frame...
|
||||||
|
gThreadClient.eval(frame0.actor, "arg", function(aResponse) {
|
||||||
|
do_check_eq(aResponse.type, "resumed");
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
// 'arg' should have been evaluated in frame0
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.type, "clientEvaluated");
|
||||||
|
do_check_eq(aPacket.why.frameFinished.return, "arg0");
|
||||||
|
|
||||||
|
// Now eval against the second frame.
|
||||||
|
gThreadClient.eval(frame1.actor, "arg", function(aResponse) {
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
// 'arg' should have been evaluated in frame1
|
||||||
|
do_check_eq(aPacket.type, "paused");
|
||||||
|
do_check_eq(aPacket.why.frameFinished.return, "arg1");
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function frame0(arg) {
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
function frame1(arg) {
|
||||||
|
frame0("arg0");
|
||||||
|
}
|
||||||
|
frame1("arg1");
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check pauses within evals.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_syntax_error_eval();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_syntax_error_eval()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
gThreadClient.eval(null, "debugger", function(aResponse) {
|
||||||
|
// Expect a resume then a debuggerStatement pause.
|
||||||
|
do_check_eq(aResponse.type, "resumed");
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "debuggerStatement");
|
||||||
|
// Resume from the debugger statement should immediately re-pause
|
||||||
|
// with a clientEvaluated reason.
|
||||||
|
gThreadClient.resume(function(aPacket) {
|
||||||
|
do_check_eq(aPacket.type, "resumed");
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
do_check_eq(aPacket.why.type, "clientEvaluated");
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(arg) {
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
stopMe();
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that we get a frame actor along with a debugger statement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
do_check_true(!!aPacket.frame);
|
||||||
|
do_check_true(!!aPacket.frame.actor);
|
||||||
|
do_check_eq(aPacket.frame.callee.name, "stopMe");
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe() {
|
||||||
|
debugger;
|
||||||
|
};
|
||||||
|
stopMe();
|
||||||
|
")"
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that two pauses in a row will keep the same frame actor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket1) {
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket2) {
|
||||||
|
do_check_eq(aPacket1.frame.actor, aPacket2.frame.actor);
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe() {
|
||||||
|
debugger;
|
||||||
|
debugger;
|
||||||
|
};
|
||||||
|
stopMe();
|
||||||
|
")"
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a frame actor is properly expired when the frame goes away.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket1) {
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket2) {
|
||||||
|
let poppedFrames = aPacket2.poppedFrames;
|
||||||
|
do_check_eq(typeof(poppedFrames), typeof([]));
|
||||||
|
do_check_true(poppedFrames.indexOf(aPacket1.frame.actor) >= 0);
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe() {
|
||||||
|
debugger;
|
||||||
|
};
|
||||||
|
stopMe();
|
||||||
|
debugger;
|
||||||
|
")"
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the "frames" request on the thread.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gFrames = [
|
||||||
|
// Function calls...
|
||||||
|
{ type: "call", callee: { name: "depth3" } },
|
||||||
|
{ type: "call", callee: { name: "depth2" } },
|
||||||
|
{ type: "call", callee: { name: "depth1" } },
|
||||||
|
|
||||||
|
// Anonymous function call in our eval...
|
||||||
|
{ type: "call", callee: { name: undefined } },
|
||||||
|
|
||||||
|
// The eval itself.
|
||||||
|
{ type: "eval", callee: { name: undefined } },
|
||||||
|
];
|
||||||
|
|
||||||
|
var gSliceTests = [
|
||||||
|
{ start: 0, count: undefined, resetActors: true },
|
||||||
|
{ start: 0, count: 1 },
|
||||||
|
{ start: 2, count: 2 },
|
||||||
|
{ start: 1, count: 15 },
|
||||||
|
{ start: 15, count: undefined },
|
||||||
|
];
|
||||||
|
|
||||||
|
function test_frame_slice() {
|
||||||
|
if (gSliceTests.length == 0) {
|
||||||
|
gThreadClient.resume(function() { finishClient(gClient); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let test = gSliceTests.shift();
|
||||||
|
gThreadClient.getFrames(test.start, test.count, function(aResponse) {
|
||||||
|
var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined);
|
||||||
|
do_check_eq(testFrames.length, aResponse.frames.length);
|
||||||
|
for (var i = 0; i < testFrames.length; i++) {
|
||||||
|
let expected = testFrames[i];
|
||||||
|
let actual = aResponse.frames[i];
|
||||||
|
|
||||||
|
if (test.resetActors) {
|
||||||
|
expected.actor = actual.actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
for each (let key in ["type", "callee-name"]) {
|
||||||
|
do_check_eq(expected[key], actual[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test_frame_slice();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket1) {
|
||||||
|
test_frame_slice();
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function depth3() {
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
function depth2() {
|
||||||
|
depth3();
|
||||||
|
}
|
||||||
|
function depth1() {
|
||||||
|
depth2();
|
||||||
|
};
|
||||||
|
depth1();
|
||||||
|
")"
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that frame actors retrieved with the frames request
|
||||||
|
* are included in the pause packet's popped-frames property.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_frame_slice() {
|
||||||
|
if (gSliceTests.length == 0) {
|
||||||
|
gThreadClient.resume(function() { finishClient(gClient); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let test = gSliceTests.shift();
|
||||||
|
gThreadClient.getFrames(test.start, test.count, function(aResponse) {
|
||||||
|
var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined);
|
||||||
|
do_check_eq(testFrames.length, aResponse.frames.length);
|
||||||
|
for (var i = 0; i < testFrames.length; i++) {
|
||||||
|
let expected = testFrames[i];
|
||||||
|
let actual = aResponse.frames[i];
|
||||||
|
|
||||||
|
if (test.resetActors) {
|
||||||
|
expected.actor = actual.actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in expected) {
|
||||||
|
do_check_eq(expected[key], actual[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test_frame_slice();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket1) {
|
||||||
|
gThreadClient.getFrames(0, null, function(aFrameResponse) {
|
||||||
|
do_check_eq(aFrameResponse.frames.length, 5);
|
||||||
|
// Now wait for the next pause, after which the three
|
||||||
|
// youngest actors should be popped..
|
||||||
|
let expectPopped = [frame.actor for each (frame in aFrameResponse.frames.slice(0, 3))];
|
||||||
|
expectPopped.sort()
|
||||||
|
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPausePacket) {
|
||||||
|
let popped = aPausePacket.poppedFrames.sort();
|
||||||
|
do_check_eq(popped.length, 3);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
do_check_eq(expectPopped[i], popped[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gThreadClient.resume(function() { finishClient(gClient); });
|
||||||
|
});
|
||||||
|
gThreadClient.resume();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function depth3() {
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
function depth2() {
|
||||||
|
depth3();
|
||||||
|
}
|
||||||
|
function depth1() {
|
||||||
|
depth2();
|
||||||
|
};
|
||||||
|
depth1();
|
||||||
|
debugger;
|
||||||
|
")"
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a frame actor's arguments property.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let args = aPacket.frame["arguments"];
|
||||||
|
do_check_eq(args.length, 6);
|
||||||
|
do_check_eq(args[0], 42);
|
||||||
|
do_check_eq(args[1], true);
|
||||||
|
do_check_eq(args[2], "nasu");
|
||||||
|
do_check_eq(args[3].type, "null");
|
||||||
|
do_check_eq(args[4].type, "undefined");
|
||||||
|
do_check_eq(args[5].type, "object");
|
||||||
|
do_check_eq(args[5].class, "Object");
|
||||||
|
do_check_true(!!args[5].actor);
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) {
|
||||||
|
debugger;
|
||||||
|
};
|
||||||
|
stopMe(42, true, "nasu", null, undefined, { foo: "bar" });
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a frame actor's bindings property.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let bindings = aPacket.frame.environment.bindings;
|
||||||
|
let args = bindings.arguments;
|
||||||
|
let vars = bindings.variables;
|
||||||
|
|
||||||
|
do_check_eq(args.length, 6);
|
||||||
|
do_check_eq(args[0].aNumber.value, 42);
|
||||||
|
do_check_eq(args[1].aBool.value, true);
|
||||||
|
do_check_eq(args[2].aString.value, "nasu");
|
||||||
|
do_check_eq(args[3].aNull.value.type, "null");
|
||||||
|
do_check_eq(args[4].aUndefined.value.type, "undefined");
|
||||||
|
do_check_eq(args[5].aObject.value.type, "object");
|
||||||
|
do_check_eq(args[5].aObject.value.class, "Object");
|
||||||
|
do_check_true(!!args[5].aObject.value.actor);
|
||||||
|
|
||||||
|
do_check_eq(vars.a.value, 1);
|
||||||
|
do_check_eq(vars.b.value, true);
|
||||||
|
do_check_eq(vars.c.value.type, "object");
|
||||||
|
do_check_eq(vars.c.value.class, "Object");
|
||||||
|
do_check_true(!!vars.c.value.actor);
|
||||||
|
|
||||||
|
let objClient = gThreadClient.pauseGrip(vars.c.value);
|
||||||
|
objClient.getPrototypeAndProperties(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.ownProperties.a.configurable, true);
|
||||||
|
do_check_eq(aResponse.ownProperties.a.enumerable, true);
|
||||||
|
do_check_eq(aResponse.ownProperties.a.writable, true);
|
||||||
|
do_check_eq(aResponse.ownProperties.a.value, "a");
|
||||||
|
|
||||||
|
do_check_eq(aResponse.ownProperties.b.configurable, true);
|
||||||
|
do_check_eq(aResponse.ownProperties.b.enumerable, true);
|
||||||
|
do_check_eq(aResponse.ownProperties.b.writable, true);
|
||||||
|
do_check_eq(aResponse.ownProperties.b.value.type, "undefined");
|
||||||
|
do_check_false("class" in aResponse.ownProperties.b.value);
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) {
|
||||||
|
var a = 1;
|
||||||
|
var b = true;
|
||||||
|
var c = { a: "a", b: undefined };
|
||||||
|
debugger;
|
||||||
|
};
|
||||||
|
stopMe(42, true, "nasu", null, undefined, { foo: "bar" });
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a frame actor's parent bindings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let parentEnv = aPacket.frame.environment.parent;
|
||||||
|
let bindings = parentEnv.bindings;
|
||||||
|
let args = bindings.arguments;
|
||||||
|
let vars = bindings.variables;
|
||||||
|
do_check_neq(parentEnv, undefined);
|
||||||
|
do_check_eq(args.length, 0);
|
||||||
|
do_check_eq(vars.stopMe.value.type, "object");
|
||||||
|
do_check_eq(vars.stopMe.value.class, "Function");
|
||||||
|
do_check_true(!!vars.stopMe.value.actor);
|
||||||
|
|
||||||
|
parentEnv = parentEnv.parent;
|
||||||
|
do_check_neq(parentEnv, undefined);
|
||||||
|
let objClient = gThreadClient.pauseGrip(parentEnv.object);
|
||||||
|
objClient.getPrototypeAndProperties(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.ownProperties.Object.value.type, "object");
|
||||||
|
do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
|
||||||
|
do_check_true(!!aResponse.ownProperties.Object.value.actor);
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) {
|
||||||
|
var a = 1;
|
||||||
|
var b = true;
|
||||||
|
var c = { a: "a" };
|
||||||
|
debugger;
|
||||||
|
};
|
||||||
|
stopMe(42, true, "nasu", null, undefined, { foo: "bar" });
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a |with| frame actor's bindings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let env = aPacket.frame.environment;
|
||||||
|
do_check_neq(env, undefined);
|
||||||
|
|
||||||
|
let parentEnv = env.parent;
|
||||||
|
do_check_neq(parentEnv, undefined);
|
||||||
|
|
||||||
|
let bindings = parentEnv.bindings;
|
||||||
|
let args = bindings.arguments;
|
||||||
|
let vars = bindings.variables;
|
||||||
|
do_check_eq(args.length, 1);
|
||||||
|
do_check_eq(args[0].aNumber.value, 10);
|
||||||
|
do_check_eq(vars.r.value, 10);
|
||||||
|
do_check_eq(vars.a.value, Math.PI * 100);
|
||||||
|
do_check_eq(vars.arguments.value.class, "Arguments");
|
||||||
|
do_check_true(!!vars.arguments.value.actor);
|
||||||
|
|
||||||
|
let objClient = gThreadClient.pauseGrip(env.object);
|
||||||
|
objClient.getPrototypeAndProperties(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
|
||||||
|
do_check_eq(aResponse.ownProperties.cos.value.type, "object");
|
||||||
|
do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
|
||||||
|
do_check_true(!!aResponse.ownProperties.cos.value.actor);
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(aNumber) {
|
||||||
|
var a;
|
||||||
|
var r = aNumber;
|
||||||
|
with (Math) {
|
||||||
|
a = PI * r * r;
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stopMe(10);
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the environment bindongs of a |with| within a |with|.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let env = aPacket.frame.environment;
|
||||||
|
do_check_neq(env, undefined);
|
||||||
|
|
||||||
|
let objClient = gThreadClient.pauseGrip(env.object);
|
||||||
|
objClient.getPrototypeAndProperties(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.ownProperties.one.value, 1);
|
||||||
|
do_check_eq(aResponse.ownProperties.two.value, 2);
|
||||||
|
do_check_eq(aResponse.ownProperties.foo, undefined);
|
||||||
|
|
||||||
|
let parentEnv = env.parent;
|
||||||
|
do_check_neq(parentEnv, undefined);
|
||||||
|
|
||||||
|
let parentClient = gThreadClient.pauseGrip(parentEnv.object);
|
||||||
|
parentClient.getPrototypeAndProperties(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
|
||||||
|
do_check_eq(aResponse.ownProperties.cos.value.type, "object");
|
||||||
|
do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
|
||||||
|
do_check_true(!!aResponse.ownProperties.cos.value.actor);
|
||||||
|
|
||||||
|
parentEnv = parentEnv.parent;
|
||||||
|
do_check_neq(parentEnv, undefined);
|
||||||
|
|
||||||
|
let bindings = parentEnv.bindings;
|
||||||
|
let args = bindings.arguments;
|
||||||
|
let vars = bindings.variables;
|
||||||
|
do_check_eq(args.length, 1);
|
||||||
|
do_check_eq(args[0].aNumber.value, 10);
|
||||||
|
do_check_eq(vars.r.value, 10);
|
||||||
|
do_check_eq(vars.a.value, Math.PI * 100);
|
||||||
|
do_check_eq(vars.arguments.value.class, "Arguments");
|
||||||
|
do_check_true(!!vars.arguments.value.actor);
|
||||||
|
do_check_eq(vars.foo.value, 2 * Math.PI);
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
function stopMe(aNumber) {
|
||||||
|
var a, obj = { one: 1, two: 2 };
|
||||||
|
var r = aNumber;
|
||||||
|
with (Math) {
|
||||||
|
a = PI * r * r;
|
||||||
|
with (obj) {
|
||||||
|
var foo = two * PI;
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stopMe(10);
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the environment bindings of a |with| in global scope.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let env = aPacket.frame.environment;
|
||||||
|
do_check_neq(env, undefined);
|
||||||
|
|
||||||
|
let objClient = gThreadClient.pauseGrip(env.object);
|
||||||
|
objClient.getPrototypeAndProperties(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
|
||||||
|
do_check_eq(aResponse.ownProperties.cos.value.type, "object");
|
||||||
|
do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
|
||||||
|
do_check_true(!!aResponse.ownProperties.cos.value.actor);
|
||||||
|
|
||||||
|
let parentEnv = env.parent;
|
||||||
|
do_check_neq(parentEnv, undefined);
|
||||||
|
|
||||||
|
let parentClient = gThreadClient.pauseGrip(parentEnv.object);
|
||||||
|
parentClient.getPrototypeAndProperties(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.ownProperties.a.value, Math.PI * 100);
|
||||||
|
do_check_eq(aResponse.ownProperties.r.value, 10);
|
||||||
|
do_check_eq(aResponse.ownProperties.Object.value.type, "object");
|
||||||
|
do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
|
||||||
|
do_check_true(!!aResponse.ownProperties.Object.value.actor);
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var a, r = 10;\n" +
|
||||||
|
"with (Math) {\n" +
|
||||||
|
" a = PI * r * r;\n" +
|
||||||
|
" debugger;\n" +
|
||||||
|
"}");
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* 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");
|
||||||
|
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_banana_environment();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_banana_environment()
|
||||||
|
{
|
||||||
|
|
||||||
|
gThreadClient.addOneTimeListener("paused",
|
||||||
|
function(aEvent, aPacket) {
|
||||||
|
do_check_matches({type:"paused", frame:
|
||||||
|
{environment:
|
||||||
|
{type: "function", function: {name: "banana3"},
|
||||||
|
parent:
|
||||||
|
{type: "block", bindings: {variables: {banana3:undefined}},
|
||||||
|
parent:
|
||||||
|
{type: "function", function: {name: "banana2"},
|
||||||
|
parent:
|
||||||
|
{type:"block", bindings: {variables: {banana2:undefined}},
|
||||||
|
parent:
|
||||||
|
{type:"block", bindings: {variables: {banana2:undefined}},
|
||||||
|
parent:
|
||||||
|
{type:"function", function: {name: "banana"}}}}}}}}},
|
||||||
|
aPacket,
|
||||||
|
{ Object:Object, Array:Array });
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("\
|
||||||
|
function banana(x) { \n\
|
||||||
|
return function banana2(y) { \n\
|
||||||
|
return function banana3(z) { \n\
|
||||||
|
debugger; \n\
|
||||||
|
}; \n\
|
||||||
|
}; \n\
|
||||||
|
} \n\
|
||||||
|
banana('x')('y')('z'); \n\
|
||||||
|
");
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* 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-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
gThreadClient.addOneTimeListener("framesadded", function() {
|
||||||
|
do_check_eq(gThreadClient.cachedFrames.length, 3);
|
||||||
|
do_check_true(gThreadClient.moreFrames);
|
||||||
|
do_check_false(gThreadClient.fillFrames(3));
|
||||||
|
|
||||||
|
do_check_true(gThreadClient.fillFrames(30));
|
||||||
|
gThreadClient.addOneTimeListener("framesadded", function() {
|
||||||
|
do_check_false(gThreadClient.moreFrames);
|
||||||
|
do_check_eq(gThreadClient.cachedFrames.length, 7);
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_check_true(gThreadClient.fillFrames(3));
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
var recurseLeft = 5;
|
||||||
|
function recurse() {
|
||||||
|
if (--recurseLeft == 0) {
|
||||||
|
debugger;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recurse();
|
||||||
|
};
|
||||||
|
recurse();
|
||||||
|
")"
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* 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-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function() {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_pause_frame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pause_frame()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
// Ask for exactly the number of frames we expect.
|
||||||
|
gThreadClient.addOneTimeListener("framesadded", function() {
|
||||||
|
do_check_false(gThreadClient.moreFrames);
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_check_true(gThreadClient.fillFrames(3));
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("(" + function() {
|
||||||
|
var recurseLeft = 1;
|
||||||
|
function recurse() {
|
||||||
|
if (--recurseLeft == 0) {
|
||||||
|
debugger;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recurse();
|
||||||
|
};
|
||||||
|
recurse();
|
||||||
|
")"
|
||||||
|
} + ")()");
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* 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() {
|
||||||
|
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_named_function();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_named_function()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let args = aPacket.frame.arguments;
|
||||||
|
|
||||||
|
do_check_eq(args[0].class, "Function");
|
||||||
|
do_check_eq(args[0].name, "stopMe");
|
||||||
|
|
||||||
|
let objClient = gThreadClient.pauseGrip(args[0]);
|
||||||
|
objClient.getParameterNames(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.parameterNames.length, 1);
|
||||||
|
do_check_eq(aResponse.parameterNames[0], "arg1");
|
||||||
|
|
||||||
|
gThreadClient.resume(test_inferred_name_function);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("stopMe(stopMe)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_inferred_name_function() {
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let args = aPacket.frame.arguments;
|
||||||
|
|
||||||
|
do_check_eq(args[0].class, "Function");
|
||||||
|
// No name for an anonymous function, but it should have an inferred name.
|
||||||
|
do_check_eq(args[0].name, undefined);
|
||||||
|
do_check_eq(args[0].displayName, "o.m");
|
||||||
|
|
||||||
|
let objClient = gThreadClient.pauseGrip(args[0]);
|
||||||
|
objClient.getParameterNames(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.parameterNames.length, 3);
|
||||||
|
do_check_eq(aResponse.parameterNames[0], "foo");
|
||||||
|
do_check_eq(aResponse.parameterNames[1], "bar");
|
||||||
|
do_check_eq(aResponse.parameterNames[2], "baz");
|
||||||
|
|
||||||
|
gThreadClient.resume(test_anonymous_function);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var o = { m: function(foo, bar, baz) { } }; stopMe(o.m)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_anonymous_function() {
|
||||||
|
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
|
||||||
|
let args = aPacket.frame.arguments;
|
||||||
|
|
||||||
|
do_check_eq(args[0].class, "Function");
|
||||||
|
// No name for an anonymous function, and no inferred name, either.
|
||||||
|
do_check_eq(args[0].name, undefined);
|
||||||
|
do_check_eq(args[0].displayName, undefined);
|
||||||
|
|
||||||
|
let objClient = gThreadClient.pauseGrip(args[0]);
|
||||||
|
objClient.getParameterNames(function(aResponse) {
|
||||||
|
do_check_eq(aResponse.parameterNames.length, 3);
|
||||||
|
do_check_eq(aResponse.parameterNames[0], "foo");
|
||||||
|
do_check_eq(aResponse.parameterNames[1], "bar");
|
||||||
|
do_check_eq(aResponse.parameterNames[2], "baz");
|
||||||
|
|
||||||
|
gThreadClient.resume(function() {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("stopMe(function(foo, bar, baz) { })");
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
Components.utils.import("resource://gre/modules/jsdebugger.jsm");
|
||||||
|
addDebuggerToGlobal(this);
|
||||||
|
var xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
|
||||||
|
var g = testGlobal("test1");
|
||||||
|
|
||||||
|
var dbg = new Debugger();
|
||||||
|
dbg.uncaughtExceptionHook = testExceptionHook;
|
||||||
|
|
||||||
|
dbg.addDebuggee(g);
|
||||||
|
dbg.onDebuggerStatement = function(aFrame) {
|
||||||
|
do_check_true(aFrame === dbg.getNewestFrame());
|
||||||
|
// Execute from the nested event loop, dbg.getNewestFrame() won't
|
||||||
|
// be working anymore.
|
||||||
|
|
||||||
|
do_execute_soon(function() {
|
||||||
|
try {
|
||||||
|
do_check_true(aFrame === dbg.getNewestFrame());
|
||||||
|
} finally {
|
||||||
|
xpcInspector.exitNestedEventLoop("test");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xpcInspector.enterNestedEventLoop("test");
|
||||||
|
};
|
||||||
|
|
||||||
|
g.eval("function debuggerStatement() { debugger; }; debuggerStatement();");
|
||||||
|
|
||||||
|
dbg.enabled = false;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
var gClient;
|
||||||
|
var gDebuggee;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = testGlobal("test-1");
|
||||||
|
DebuggerServer.addTestGlobal(gDebuggee);
|
||||||
|
|
||||||
|
let transport = DebuggerServer.connectPipe();
|
||||||
|
gClient = new DebuggerClient(transport);
|
||||||
|
gClient.connect(function(aType, aTraits) {
|
||||||
|
attachTestTab(gClient, "test-1", test_attach);
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_attach(aResponse, aTabClient)
|
||||||
|
{
|
||||||
|
gClient.attachThread(aResponse.threadActor, function(aResponse, aThreadClient) {
|
||||||
|
do_check_eq(aThreadClient.paused, true);
|
||||||
|
aThreadClient.resume(function() {
|
||||||
|
test_interrupt();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_interrupt()
|
||||||
|
{
|
||||||
|
do_check_eq(gClient.activeThread.paused, false);
|
||||||
|
gClient.activeThread.interrupt(function(aResponse) {
|
||||||
|
do_check_eq(gClient.activeThread.paused, true);
|
||||||
|
gClient.activeThread.resume(function() {
|
||||||
|
do_check_eq(gClient.activeThread.paused, false);
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup()
|
||||||
|
{
|
||||||
|
gClient.addListener("closed", function(aEvent) {
|
||||||
|
do_test_finished();
|
||||||
|
});
|
||||||
|
gClient.close();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check basic getSources functionality.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
var gNumTimesSourcesSent = 0;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.request = (function (request) {
|
||||||
|
return function (aRequest, aOnResponse) {
|
||||||
|
if (aRequest.type === "sources") {
|
||||||
|
++gNumTimesSourcesSent;
|
||||||
|
}
|
||||||
|
return request.call(this, aRequest, aOnResponse);
|
||||||
|
};
|
||||||
|
}(gClient.request));
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_simple_listsources();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_simple_listsources()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
gThreadClient.getSources(function (aResponse) {
|
||||||
|
do_check_true(aResponse.sources.some(function (s) {
|
||||||
|
return s.url.match(/test_listsources-01.js$/);
|
||||||
|
}));
|
||||||
|
|
||||||
|
do_check_true(gNumTimesSourcesSent <= 1,
|
||||||
|
"Should only send one sources request at most, even though we"
|
||||||
|
+ " might have had to send one to determine feature support.");
|
||||||
|
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
|
||||||
|
"debugger;\n" + // line0 + 1
|
||||||
|
"var a = 1;\n" + // line0 + 2
|
||||||
|
"var b = 2;\n"); // line0 + 3
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check getting sources before there are any.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
var gNumTimesSourcesSent = 0;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-stack");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.request = (function (request) {
|
||||||
|
return function (aRequest, aOnResponse) {
|
||||||
|
if (aRequest.type === "sources") {
|
||||||
|
++gNumTimesSourcesSent;
|
||||||
|
}
|
||||||
|
return request.call(this, aRequest, aOnResponse);
|
||||||
|
};
|
||||||
|
}(gClient.request));
|
||||||
|
// Make sure that the eval script from addTestGlobal() won't interfere with
|
||||||
|
// the test.
|
||||||
|
gc();
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_listing_zero_sources();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_listing_zero_sources()
|
||||||
|
{
|
||||||
|
gThreadClient.getSources(function (aPacket) {
|
||||||
|
do_check_true(!aPacket.error);
|
||||||
|
do_check_true(!!aPacket.sources);
|
||||||
|
do_check_eq(aPacket.sources.length, 0);
|
||||||
|
|
||||||
|
do_check_true(gNumTimesSourcesSent <= 1,
|
||||||
|
"Should only send one sources request at most, even though we"
|
||||||
|
+ " might have had to send one to determine feature support.");
|
||||||
|
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check getSources functionality when there are lots of sources.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var gDebuggee;
|
||||||
|
var gClient;
|
||||||
|
var gThreadClient;
|
||||||
|
|
||||||
|
function run_test()
|
||||||
|
{
|
||||||
|
initTestDebuggerServer();
|
||||||
|
gDebuggee = addTestGlobal("test-sources");
|
||||||
|
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
gClient.connect(function () {
|
||||||
|
attachTestTabAndResume(gClient, "test-sources", function (aResponse, aTabClient, aThreadClient) {
|
||||||
|
gThreadClient = aThreadClient;
|
||||||
|
test_simple_listsources();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
do_test_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_simple_listsources()
|
||||||
|
{
|
||||||
|
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
|
||||||
|
gThreadClient.getSources(function (aResponse) {
|
||||||
|
do_check_true(
|
||||||
|
!aResponse.error,
|
||||||
|
"There shouldn't be an error fetching large amounts of sources.");
|
||||||
|
|
||||||
|
do_check_true(aResponse.sources.some(function (s) {
|
||||||
|
return s.url.match(/foo-999.js$/);
|
||||||
|
}));
|
||||||
|
|
||||||
|
gThreadClient.resume(function () {
|
||||||
|
finishClient(gClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
Cu.evalInSandbox("function foo###() {return ###;}".replace(/###/g, i),
|
||||||
|
gDebuggee,
|
||||||
|
"1.8",
|
||||||
|
"http://example.com/foo-" + i + ".js",
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
gDebuggee.eval("debugger;");
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/* -*- 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()
|
||||||
|
{
|
||||||
|
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||||
|
addDebuggerToGlobal(this);
|
||||||
|
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||||
|
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
||||||
|
loader.loadSubScript("resource://gre/modules/devtools/server/actors/script.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));
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, 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.pauseLongString(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 + '")');
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue