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 resolutionIt = _searchResolutionsOrderArray.begin(); resolutionIt != _searchResolutionsOrderArray.end(); ++resolutionIt) {
|
||||
|
||||
fullpath = this->getPathForFilename(newFilename, *resolutionIt, *searchIt);
|
||||
|
||||
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
|
||||
pDirector->setAnimationInterval(1.0 / 60);
|
||||
|
||||
FileUtils::getInstance()->addSearchPath("res");
|
||||
FileUtils::getInstance()->addSearchPath("js");
|
||||
|
||||
ScriptingCore* sc = ScriptingCore::getInstance();
|
||||
sc->addRegisterCallback(register_all_cocos2dx);
|
||||
sc->addRegisterCallback(register_all_cocos2dx_extension);
|
||||
|
@ -55,8 +58,6 @@ bool AppDelegate::applicationDidFinishLaunching()
|
|||
|
||||
sc->start();
|
||||
|
||||
FileUtils::getInstance()->addSearchPath("res");
|
||||
|
||||
auto pEngine = ScriptingCore::getInstance();
|
||||
ScriptEngineManager::getInstance()->setScriptEngine(pEngine);
|
||||
#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()) {
|
||||
JSString* globalName = JSVAL_TO_STRING(argv[1]);
|
||||
JSStringWrapper name(globalName);
|
||||
js::RootedObject* rootedGlobal = globals[name];
|
||||
if (rootedGlobal) {
|
||||
res = ScriptingCore::getInstance()->runScript(path, rootedGlobal->get());
|
||||
// js::RootedObject* rootedGlobal = globals[name];
|
||||
JSObject* debugObj = ScriptingCore::getInstance()->getDebugGlobal();
|
||||
if (debugObj) {
|
||||
res = ScriptingCore::getInstance()->runScript(path, debugObj);
|
||||
} else {
|
||||
JS_ReportError(cx, "Invalid global object: %s", (char*)name);
|
||||
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_, "_unlockVM", JSBDebug_UnlockExecution, 0, JSPROP_READONLY | JSPROP_PERMANENT);
|
||||
|
||||
|
||||
runScript("jsb_debugger.js", debugGlobal_);
|
||||
// runScript("SysTest/script.js", debugGlobal_);
|
||||
|
||||
CCLOG("before _prepareDebugger...");
|
||||
// prepare the debugger
|
||||
|
@ -2266,7 +2267,6 @@ static void serverEntryPoint(void)
|
|||
|
||||
listen(s, 1);
|
||||
|
||||
int recieveIndex = 0;
|
||||
while (true) {
|
||||
clientSocket = accept(s, NULL, NULL);
|
||||
|
||||
|
@ -2284,144 +2284,13 @@ static void serverEntryPoint(void)
|
|||
// process any input, send any output
|
||||
clearBuffers();
|
||||
|
||||
// if (recieveIndex == 0)
|
||||
// {
|
||||
// replyToClient(clientSocket, "{\"from\":\"root\",\"applicationType\":\"browser\",\"traits\":{\"sources\": true}}");
|
||||
// ++recieveIndex;
|
||||
// }
|
||||
|
||||
|
||||
char buf[1024] = {0};
|
||||
int readBytes = 0;
|
||||
while ((readBytes = ::recv(clientSocket, buf, sizeof(buf), 0)) > 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
|
||||
inData.append(buf);
|
||||
// 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