issue #2905: add missing files

This commit is contained in:
minggo 2013-10-12 13:44:57 +08:00
parent ad6325b0dd
commit 5acbe663e0
10 changed files with 4974 additions and 195 deletions

View File

@ -1,131 +0,0 @@
# cocos2d-x C++ coding sytle
## Detailed information
Please, refer to this document for a detailed version of the cocos2d-x C++ coding sytle:
* [cocos2d-x c++ coding style](http://www.cocos2d-x.org/wiki/Cocos2d_c++_coding_style)
## Quick Sample
Use this sample as a quick reference. But DO READ the detailed doc for more info.
Header file:
```c++
/**
* We use Doxygen strings for documentation.
* All public classes, methods, structs should be documented using Doxygen Strings
*/
class CC_DLL Sprite : public NodeRGBA, public TextureProtocol
{ /* class braces start in a new line */
/* no indentation here for public, protected or private */
/* First add all the "public" stuff, then all the "protected" stuff, and finally all the "private" stuff
public:
/* we don't use tabs, we use spaces, and we use a 4 space identation */
/* 1st add all static const */
static const int INDEX_NOT_INITIALIZED = -1;
/* then add all the creators and other relevant static methods */
static Sprite* create();
static Sprite* create(const char *filename);
static Sprite* create(const char *filename, const Rect& rect);
/* if applicable, then add the consturctors / destructors */
Sprite();
virtual ~Sprite(void);
/* then add all the initialization methods */
/* notice that they are in the same order as the creators */
virtual bool init(void);
virtual bool initWithTexture(Texture2D *texture);
virtual bool initWithTexture(Texture2D *texture, const Rect& rect);
/* then add the regular instace methods */
virtual void updateTransform(void);
virtual SpriteBatchNode* getBatchNode(void);
virtual void setBatchNode(SpriteBatchNode *spriteBatchNode);
/* then add all the overriden methods */
/* notice that all overriden methods must use the 'override' keyword */
/* overriden methods are not forced to have doxygen strings UNLESS they change the behavior in a non obvios way */
virtual void setPosition(const Point& pos) override;
virtual void setRotation(float rotation) override;
virtual void setRotationX(float rotationX) override;
/* once you finish with the 'public' methods, start with the 'protected' ones */
protected:
/* protected methods are not forced to have Doxygen strings, but if they have it, better */
void updateColor(void);
virtual void setTextureCoords(Rect rect);
/* After adding all the methods, add the ivars */
/* all ivars must start with _ */
/* Do not use Hungarian notation */
TextureAtlas* _textureAtlas;
int _atlasIndex;
SpriteBatchNode* _batchNode;
};
```
Implementation file:
```c++
/* Do not use doxygen comments on the implementation file */
/* The methos MUST be in the same order as where declared in the header file */
Sprite* Sprite::create(const char *filename)
{
/* Don't use tabs. Use spaces. Use 4-space indentation */
Sprite *sprite = new Sprite();
/* put curly braces in the same line as in the 'if'*/
/* leave a space between the 'if' and the '(' */
/* don't leave spaces between '()' */
if (sprite && sprite->initWithFile(filename)) {
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return NULL;
}
/* Initialization list can be indented to 0 spaces, or to 4 spaces. If in doubt, be consistent with the indentation already used in the file */
/* Only use the Initialization lists for types that can't fail when initialized */
Sprite::Sprite()
: _shouldBeHidden(false)
, _texture(nullptr)
, _physicsBody(nullptr)
{
}
/* use the 'initXXX' methods to initialize types that might fail */
bool Sprite::initWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
{
/* it ok not use use curly braces */
if (something)
do_something();
else
something_else();
/* but if you use curly branches in one branch, all the branches should use curly branches */
if (something) {
do_something1();
do_something2();
} else {
so_something_else();
}
}
```

View File

@ -1,60 +0,0 @@
* Need to figure out how to get correct screen resolutions consistently.
* Touch handler screwed up after TouchesTest? MenuTest? (TestCpp sample)
* TTF Font rendering is slow and seems to wrap around by a few pixels
horizontally. Need to investigate here, but I suspect the right answer is to
offload font rendering to an offscreen canvas and let the browser handle it.
Potentially creates new challenges in packaging which would need to be solved
somehow.
###
DONE:
###
* Need to switch to server-side buffers in
- cocos2dx/draw_nodes/CCDrawingPrimitives
* Need to compile with -O2 -- possible -s VERBOSE=1 will give a clue?
* SchedulerTest crashes
- cocos2d::CCNode::boundingBox()
* Parallax Test crashes
- cocos2d::CCAtlasNode::calculateMaxItems()
* Particle Test crashes
- TIFFClientOpen not a function. Looks like we need to build and link in libtiff.
* cocos2d::CCAtlasNode::calculateMaxItems() throwing an error; related to
CCTextureAtlas? Preventing particle test from working.
* Need to switch to server-side buffers in
- cocos2dx/draw_nodes/CCDrawNode.cpp -- Think this is fixed by undef CC_TEXTURE_ATLAS_USE_VAO?
- cocos2dx/particle_nodes/CCParticleSystemQuad.cpp -- Think this is fixed by CC_REBIND_INDICES_BUFFER?
- cocos2dx/textures/CCTextureAtlas.cpp -- Think this works already.
* Layer Test crashes
- cocos2d::CCLabelBMFont::create(char const*, char const*, float, cocos2d::CCTextAlignment, cocos2d::CCPoint)
* IntervalTest crashes
- cocos2d::CCLabelBMFont::create(char const*, char const*, float, cocos2d::CCTextAlignment, cocos2d::CCPoint)
* TileMap Test crashes
- void CCNode::insertChild(CCNode* child, int z)
* LabelTest crashes
- cocos2d::CCAtlasNode::calculateMaxItems()
* ZwoptexTest crashes.
- ZwoptexGenericTest::onEnter()
* ChipmunkTest crashes:
Aborting due to Chipmunk error: Moment of Inertia must be positive and non-zero.
Failed condition: moment > 0.0f
Source:../src/cpBody.c:151
* Add linkage to libjpeg to get RenderTexture test to work.

View File

@ -1,4 +0,0 @@
===== cocos2d-x 3.0 Release Notes =====
Please, read the online release notes document:
http://www.cocos2d-x.org/wiki/Release_Notes_for_Cocos2d-x_v300

View File

@ -0,0 +1,133 @@
/* 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";
function utf16to8(str) {
var out, i, len, c;
out = "";
len = str.length;
for(i = 0; i < len; i++)
{
c = str.charCodeAt(i);
if ((c >= 0x0001) && (c <= 0x007F))
{
out += str.charAt(i);
}
else if (c > 0x07FF)
{
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}
else
{
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}
}
return out;
}
function utf8to16(str) {
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while(i < len) { c = str.charCodeAt(i++); switch(c >> 4)
{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += str.charAt(i-1);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
}
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,54 @@
Remote Debugging By Using FireFox
=================================
Requirement
-----------
* Firefox: From v24
How To Use
----------
### Prepare ###
Please refer to https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging .
### Enable Debugger Support For Your JSB Project ###
```
bool AppDelegate::applicationDidFinishLaunching()
{
...
ScriptingCore* sc = ScriptingCore::getInstance();
sc->addRegisterCallback(register_all_cocos2dx);
sc->addRegisterCallback(register_all_cocos2dx_extension);
sc->addRegisterCallback(register_cocos2dx_js_extensions);
sc->addRegisterCallback(jsb_register_chipmunk);
sc->addRegisterCallback(register_all_cocos2dx_extension_manual);
sc->addRegisterCallback(register_CCBuilderReader);
sc->addRegisterCallback(jsb_register_system);
sc->addRegisterCallback(JSB_register_opengl);
sc->start();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
sc->enableDebugger(); // Enable debugger here
#endif
...
}
```
Run your game.
### Open Firefox And Follow The Step As Follows ###
![pic 1](https://lh5.googleusercontent.com/-HoxLGBdV2J0/UlZ7ZoFUjyI/AAAAAAAAADM/68GDaCQ1vP0/s0-I/Firefox-Remote-Debug01.jpg)
![pic 2](https://lh6.googleusercontent.com/-7FDIHAYsKAY/UlZ7Yf8W-pI/AAAAAAAAAFQ/joG0AymnuBk/s0-I/Firefox-Remote-Debug02.jpg)
![pic 3](https://lh4.googleusercontent.com/-idvnMRGcGy8/UlZ7Wj6DDuI/AAAAAAAAAC0/L9IVyHLNqeQ/s0-I/Firefox-Remote-Debug04.jpg)
![pic 4](https://lh6.googleusercontent.com/-YuZj7JGAtFE/UlZ9DDGDczI/AAAAAAAAAEQ/D2qIedjP5FU/s0-I/Firefox-Remote-Debug04.png.png)
![pic 5](https://lh3.googleusercontent.com/-cdIcNa3jT5c/UlZ9uapf3OI/AAAAAAAAAEg/MGq3vLHsauw/s0-I/Firefox-Remote-Debug05.png)
![pic 6](https://lh5.googleusercontent.com/-T79-o5ylJKI/UlZ_JJQe3MI/AAAAAAAAAE8/F63fSVxlJKs/s0-I/Firefox-Remote-Debug06.png)

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,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,928 @@
/* -*- 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) {
log("assert >>>> " + cond.toString());
return e;
}
}
function XPCInspector() {
this.exitNestedEventLoop = _exitNestedEventLoop;
this.enterNestedEventLoop = _enterNestedEventLoop;
this.eventLoopNestLevel = _getEventLoopNestLevel;
}
//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 = new XPCInspector();//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);
// });

View File

@ -0,0 +1,288 @@
/* -*- 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";
// Components.utils.import("resource://gre/modules/NetUtil.jsm");
/**
* An adapter that handles data transfers between the debugger client and
* server. It can work with both nsIPipe and nsIServerSocket transports so
* long as the properly created input and output streams are specified.
*
* @param aInput nsIInputStream
* The input stream.
* @param aOutput nsIAsyncOutputStream
* The output stream.
*
* Given a DebuggerTransport instance dt:
* 1) Set dt.hooks to a packet handler object (described below).
* 2) Call dt.ready() to begin watching for input packets.
* 3) Send packets as you please, and handle incoming packets passed to
* hook.onPacket.
* 4) Call dt.close() to close the connection, and disengage from the event
* loop.
*
* A packet handler object is an object with two methods:
*
* - onPacket(packet) - called when we have received a complete packet.
* |Packet| is the parsed form of the packet --- a JavaScript value, not
* a JSON-syntax string.
*
* - onClosed(status) - called when the connection is closed. |Status| is
* an nsresult, of the sort passed to nsIRequestObserver.
*
* Data is transferred as a JSON packet serialized into a string, with the
* string length prepended to the packet, followed by a colon
* ([length]:[packet]). The contents of the JSON packet are specified in
* the Remote Debugging Protocol specification.
*/
this.DebuggerTransport = function DebuggerTransport(aInput, aOutput)
{
this._input = aInput;
this._output = aOutput;
this._converter = null;//Cc["@mozilla.org/intl/scriptableunicodeconverter"]
// .createInstance(Ci.nsIScriptableUnicodeConverter);
// this._converter.charset = "UTF-8";
this._outgoing = "";
this._incoming = "";
this.hooks = null;
}
DebuggerTransport.prototype = {
/**
* Transmit a packet.
*
* This method returns immediately, without waiting for the entire
* packet to be transmitted, registering event handlers as needed to
* transmit the entire packet. Packets are transmitted in the order
* they are passed to this method.
*/
send: function DT_send(aPacket) {
// TODO (bug 709088): remove pretty printing when the protocol is done.
let data = JSON.stringify(aPacket, null, 2);
// data = this._converter.ConvertFromUnicode(data);
let data_for_len = utf16to8(data);
this._outgoing = data_for_len.length + ':' + data;
this._flushOutgoing();
},
/**
* Close the transport.
*/
close: function DT_close() {
this._input.close();
this._output.close();
},
/**
* Flush the outgoing stream.
*/
_flushOutgoing: function DT_flushOutgoing() {
if (this._outgoing.length > 0) {
// var threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
// this._output.asyncWait(this, 0, 0, threadManager.currentThread);
log("outgoing: " + this._outgoing);//.substring(0, 200));
_bufferWrite(this._outgoing);
}
},
onOutputStreamReady:
makeInfallible(function DT_onOutputStreamReady(aStream) {
let written = 0;
try {
written = aStream.write(this._outgoing, this._outgoing.length);
} catch(e if e.result == Components.results.NS_BASE_STREAM_CLOSED) {
dumpn("Connection closed.");
this.close();
return;
}
this._outgoing = this._outgoing.slice(written);
this._flushOutgoing();
}, "DebuggerTransport.prototype.onOutputStreamReady"),
/**
* Initialize the input stream for reading. Once this method has been
* called, we watch for packets on the input stream, and pass them to
* this.hook.onPacket.
*/
ready: function DT_ready() {
// let pump = Cc["@mozilla.org/network/input-stream-pump;1"]
// .createInstance(Ci.nsIInputStreamPump);
// pump.init(this._input, -1, -1, 0, 0, false);
// pump.asyncRead(this, null);
},
// nsIStreamListener
onStartRequest:
makeInfallible(function DT_onStartRequest(aRequest, aContext) {},
"DebuggerTransport.prototype.onStartRequest"),
onStopRequest:
makeInfallible(function DT_onStopRequest(aRequest, aContext, aStatus) {
this.close();
if (this.hooks) {
this.hooks.onClosed(aStatus);
this.hooks = null;
}
}, "DebuggerTransport.prototype.onStopRequest"),
onDataAvailable: makeInfallible(function DT_onDataAvailable (incoming)
// makeInfallible(function DT_onDataAvailable(aRequest, aContext,
// aStream, aOffset, aCount)
{
this._incoming = incoming;//+= NetUtil.readInputStreamToString(aStream,
// aStream.available());
while (this._processIncoming()) {};
}, "DebuggerTransport.prototype.onDataAvailable"),
/**
* Process incoming packets. Returns true if a packet has been received, either
* if it was properly parsed or not. Returns false if the incoming stream does
* not contain a full packet yet. After a proper packet is parsed, the dispatch
* handler DebuggerTransport.hooks.onPacket is called with the packet as a
* parameter.
*/
_processIncoming: function DT__processIncoming() {
// Well this is ugly.
let sep = this._incoming.indexOf(':');
if (sep < 0) {
return false;
}
let count = parseInt(this._incoming.substring(0, sep));
if (this._incoming.length - (sep + 1) < count) {
// Don't have a complete request yet.
return false;
}
// We have a complete request, pluck it out of the data and parse it.
this._incoming = this._incoming.substring(sep + 1);
let packet = this._incoming.substring(0, count);
this._incoming = this._incoming.substring(count);
try {
// packet = this._converter.ConvertToUnicode(packet);
packet = utf8to16(packet);
var parsed = JSON.parse(packet);
} catch(e) {
let msg = "Error parsing incoming packet: " + packet + " (" + e + " - " + e.stack + ")";
// if (Cu.reportError) {
// Cu.reportError(msg);
// }
dump(msg + "\n");
return true;
}
dumpn("Got: " + packet);
let self = this;
// Services.tm.currentThread.dispatch(makeInfallible(function() {
self.hooks.onPacket(parsed);
// }, "DebuggerTransport instance's this.hooks.onPacket"), 0);
return true;
}
}
/**
* An adapter that handles data transfers between the debugger client and
* server when they both run in the same process. It presents the same API as
* DebuggerTransport, but instead of transmitting serialized messages across a
* connection it merely calls the packet dispatcher of the other side.
*
* @param aOther LocalDebuggerTransport
* The other endpoint for this debugger connection.
*
* @see DebuggerTransport
*/
this.LocalDebuggerTransport = function LocalDebuggerTransport(aOther)
{
this.other = aOther;
this.hooks = null;
/*
* A packet number, shared between this and this.other. This isn't used
* by the protocol at all, but it makes the packet traces a lot easier to
* follow.
*/
this._serial = this.other ? this.other._serial : { count: 0 };
}
LocalDebuggerTransport.prototype = {
/**
* Transmit a message by directly calling the onPacket handler of the other
* endpoint.
*/
send: function LDT_send(aPacket) {
let serial = this._serial.count++;
if (wantLogging) {
if (aPacket.to) {
dumpn("Packet " + serial + " sent to " + uneval(aPacket.to));
} else if (aPacket.from) {
dumpn("Packet " + serial + " sent from " + uneval(aPacket.from));
}
}
this._deepFreeze(aPacket);
let other = this.other;
if (other) {
Services.tm.currentThread.dispatch(makeInfallible(function() {
// Avoid the cost of JSON.stringify() when logging is disabled.
if (wantLogging) {
dumpn("Received packet " + serial + ": " + JSON.stringify(aPacket, null, 2));
}
if (other.hooks) {
other.hooks.onPacket(aPacket);
}
}, "LocalDebuggerTransport instance's this.other.hooks.onPacket"), 0);
}
},
/**
* Close the transport.
*/
close: function LDT_close() {
if (this.other) {
// Remove the reference to the other endpoint before calling close(), to
// avoid infinite recursion.
let other = this.other;
delete this.other;
other.close();
}
if (this.hooks) {
this.hooks.onClosed();
this.hooks = null;
}
},
/**
* An empty method for emulating the DebuggerTransport API.
*/
ready: function LDT_ready() {},
/**
* Helper function that makes an object fully immutable.
*/
_deepFreeze: function LDT_deepFreeze(aObject) {
Object.freeze(aObject);
for (let prop in aObject) {
// Freeze the properties that are objects, not on the prototype, and not
// already frozen. Note that this might leave an unfrozen reference
// somewhere in the object if there is an already frozen object containing
// an unfrozen object.
if (aObject.hasOwnProperty(prop) && typeof aObject === "object" &&
!Object.isFrozen(aObject)) {
this._deepFreeze(o[prop]);
}
}
}
};