issue #2823: firefox debugger step ok.

This commit is contained in:
James Chen 2013-10-07 17:19:00 +08:00
parent b5679cbc3a
commit b5d02d8cca
152 changed files with 23120 additions and 153 deletions

View File

@ -1 +1 @@
3959af89b6bbc8fb982769d22d2591991107d6e5
8ad5afb135018cfa93b3aee4dddbea24f0ef9b9e

View File

@ -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)

View File

@ -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);
@ -54,8 +57,6 @@ bool AppDelegate::applicationDidFinishLaunching()
sc->addRegisterCallback(register_CCBuilderReader);
sc->start();
FileUtils::getInstance()->addSearchPath("res");
auto pEngine = ScriptingCore::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(pEngine);

View File

@ -1 +1 @@
381c282bed409f4b41a82505460d4d6c84ba70ab
0017c1d76ce42fd252ff8245774d5a276e278115

View File

@ -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;
@ -1964,9 +1965,9 @@ void ScriptingCore::enableDebugger() {
JS_DefineFunction(cx_, debugGlobal_, "_bufferRead", JSBDebug_BufferRead, 0, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx_, debugGlobal_, "_lockVM", JSBDebug_LockExecution, 2, JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(cx_, debugGlobal_, "_unlockVM", JSBDebug_UnlockExecution, 0, JSPROP_READONLY | JSPROP_PERMANENT);
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

View File

@ -1 +1 @@
db6aaa41d0756117258eebb759cd6420f66071e0
ceb78008acf831592a416d7dfa85a9dfd0ddae75

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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");

View File

@ -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

View File

@ -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);
}
});

View File

@ -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);
});

View File

@ -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");

View File

@ -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

View File

@ -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;
//
//});

View File

@ -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();

View File

@ -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
};

View File

@ -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

View File

@ -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

View File

@ -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()();
}

View File

@ -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>

View File

@ -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/.

View File

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html>
<script>
var xhr = new XMLHttpRequest;
xhr.timeout = 1742;
xhr.expando = 'Expando!';
</script>
</html>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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']

View File

@ -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");
}
}
}
};

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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);
}

View File

@ -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) {
}

View File

@ -0,0 +1,6 @@
foo = (n) ->
return "foo" + i for i in [0...n]
[first, second, third] = foo(3)
debugger

View File

@ -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"
}

View File

@ -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);

View File

@ -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());
}

View File

@ -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();
}

View File

@ -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);
});
}

View File

@ -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();
});
}

View File

@ -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();
});
}

View File

@ -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);
});
});
}

View File

@ -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();
});
});
}

View File

@ -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
}

View File

@ -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);
});
});
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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);
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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();
});
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);
}
}
}
}

View File

@ -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});
} + ")()");
}

View File

@ -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});
} + ")()");
}

View File

@ -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});
} + ")()");
}

View File

@ -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");
} + ")()");
}

View File

@ -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();
} + ")()");
}

View File

@ -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();
")"
} + ")()");
}

View File

@ -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();
")"
} + ")()");
}

View File

@ -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;
")"
} + ")()");
}

View File

@ -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();
")"
} + ")()");
}

View File

@ -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;
")"
} + ")()");
}

View File

@ -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" });
} + ")()");
}

View File

@ -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" });
} + ")()");
}

View File

@ -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" });
} + ")()");
}

View File

@ -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);
} + ")()");
}

View File

@ -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);
} + ")()");
}

View File

@ -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" +
"}");
}

View File

@ -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\
");
}

View File

@ -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();
")"
} + ")()");
}

View File

@ -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();
")"
} + ")()");
}

View File

@ -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) { })");
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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
}

View File

@ -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);
});
}

View File

@ -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;");
}

View File

@ -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));
}

View File

@ -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