mirror of
https://github.com/tiennm99/fbird.git
synced 2026-10-03 07:12:43 +00:00
init: fbird
This commit is contained in:
1 parent
103ece4073
commit
3b63c10b2c
3440 files changed
+1221398
No files matched your search
@@ -0,0 +1,833 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
/* 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";
|
||||
|
||||
/* General utilities used throughout devtools. */
|
||||
|
||||
// var { Ci, Cu, Cc, components } = require("chrome");
|
||||
// var Services = require("Services");
|
||||
// var promise = require("promise");
|
||||
|
||||
// loader.lazyRequireGetter(this, "FileUtils",
|
||||
// "resource://gre/modules/FileUtils.jsm", true);
|
||||
|
||||
function DevToolsUtils() {
|
||||
|
||||
}
|
||||
|
||||
DevToolsUtils.utf16to8 = 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;
|
||||
}
|
||||
|
||||
DevToolsUtils.utf8to16 = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the error |aError| into a string, without fail.
|
||||
*/
|
||||
DevToolsUtils.safeErrorString = function safeErrorString(aError) {
|
||||
try {
|
||||
let errorString = aError.toString();
|
||||
if (typeof errorString == "string") {
|
||||
// Attempt to attach a stack to |errorString|. If it throws an error, or
|
||||
// isn't a string, don't use it.
|
||||
try {
|
||||
if (aError.stack) {
|
||||
let stack = aError.stack.toString();
|
||||
if (typeof stack == "string") {
|
||||
errorString += "\nStack: " + stack;
|
||||
}
|
||||
}
|
||||
} catch (ee) { }
|
||||
|
||||
// Append additional line and column number information to the output,
|
||||
// since it might not be part of the stringified error.
|
||||
if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") {
|
||||
errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber;
|
||||
}
|
||||
|
||||
return errorString;
|
||||
}
|
||||
} catch (ee) { }
|
||||
|
||||
// We failed to find a good error description, so do the next best thing.
|
||||
return Object.prototype.toString.call(aError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report that |aWho| threw an exception, |aException|.
|
||||
*/
|
||||
DevToolsUtils.reportException = function reportException(aWho, aException) {
|
||||
let msg = aWho + " threw an exception: " + DevToolsUtils.safeErrorString(aException);
|
||||
|
||||
log(msg + "\n");
|
||||
|
||||
// if (Cu && 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);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.)
|
||||
*/
|
||||
DevToolsUtils.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;
|
||||
}
|
||||
return DevToolsUtils.reportException(who, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Interleaves two arrays element by element, returning the combined array, like
|
||||
* a zip. In the case of arrays with different sizes, undefined values will be
|
||||
* interleaved at the end along with the extra values of the larger array.
|
||||
*
|
||||
* @param Array a
|
||||
* @param Array b
|
||||
* @returns Array
|
||||
* The combined array, in the form [a1, b1, a2, b2, ...]
|
||||
*/
|
||||
DevToolsUtils.zip = function zip(a, b) {
|
||||
if (!b) {
|
||||
return a;
|
||||
}
|
||||
if (!a) {
|
||||
return b;
|
||||
}
|
||||
const pairs = [];
|
||||
for (let i = 0, aLength = a.length, bLength = b.length;
|
||||
i < aLength || i < bLength;
|
||||
i++) {
|
||||
pairs.push([a[i], b[i]]);
|
||||
}
|
||||
return pairs;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an object into an array with 2-element arrays as key/value
|
||||
* pairs of the object. `{ foo: 1, bar: 2}` would become
|
||||
* `[[foo, 1], [bar 2]]` (order not guaranteed);
|
||||
*
|
||||
* @param object obj
|
||||
* @returns array
|
||||
*/
|
||||
DevToolsUtils.entries = function entries(obj) {
|
||||
return Object.keys(obj).map(k => [k, obj[k]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes the given functions into a single function, which will
|
||||
* apply the results of each function right-to-left, starting with
|
||||
* applying the given arguments to the right-most function.
|
||||
* `compose(foo, bar, baz)` === `args => foo(bar(baz(args)`
|
||||
*
|
||||
* @param ...function funcs
|
||||
* @returns function
|
||||
*/
|
||||
DevToolsUtils.compose = function compose(...funcs) {
|
||||
return (...args) => {
|
||||
const initialValue = funcs[funcs.length - 1].apply(null, args);
|
||||
const leftFuncs = funcs.slice(0, -1);
|
||||
return leftFuncs.reduceRight((composed, f) => f(composed),
|
||||
initialValue);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the next tick in the event loop to execute a callback.
|
||||
*/
|
||||
DevToolsUtils.executeSoon = function executeSoon(aFn) {
|
||||
if (isWorker) {
|
||||
setImmediate(aFn);
|
||||
} else {
|
||||
Services.tm.mainThread.dispatch({
|
||||
run: DevToolsUtils.makeInfallible(aFn)
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for the next tick in the event loop.
|
||||
*
|
||||
* @return Promise
|
||||
* A promise that is resolved after the next tick in the event loop.
|
||||
*/
|
||||
DevToolsUtils.waitForTick = function waitForTick() {
|
||||
let deferred = promise.defer();
|
||||
DevToolsUtils.executeSoon(deferred.resolve);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for the specified amount of time to pass.
|
||||
*
|
||||
* @param number aDelay
|
||||
* The amount of time to wait, in milliseconds.
|
||||
* @return Promise
|
||||
* A promise that is resolved after the specified amount of time passes.
|
||||
*/
|
||||
DevToolsUtils.waitForTime = function waitForTime(aDelay) {
|
||||
let deferred = promise.defer();
|
||||
require("Timer").setTimeout(deferred.resolve, aDelay);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
|
||||
* very large arrays by yielding to the browser and continuing execution on the
|
||||
* next tick.
|
||||
*
|
||||
* @param Array aArray
|
||||
* The array being iterated over.
|
||||
* @param Function aFn
|
||||
* The function called on each item in the array. If a promise is
|
||||
* returned by this function, iterating over the array will be paused
|
||||
* until the respective promise is resolved.
|
||||
* @returns Promise
|
||||
* A promise that is resolved once the whole array has been iterated
|
||||
* over, and all promises returned by the aFn callback are resolved.
|
||||
*/
|
||||
DevToolsUtils.yieldingEach = function yieldingEach(aArray, aFn) {
|
||||
const deferred = promise.defer();
|
||||
|
||||
let i = 0;
|
||||
let len = aArray.length;
|
||||
let outstanding = [deferred.promise];
|
||||
|
||||
(function loop() {
|
||||
const start = Date.now();
|
||||
|
||||
while (i < len) {
|
||||
// Don't block the main thread for longer than 16 ms at a time. To
|
||||
// maintain 60fps, you have to render every frame in at least 16ms; we
|
||||
// aren't including time spent in non-JS here, but this is Good
|
||||
// Enough(tm).
|
||||
if (Date.now() - start > 16) {
|
||||
DevToolsUtils.executeSoon(loop);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
outstanding.push(aFn(aArray[i], i++));
|
||||
} catch (e) {
|
||||
deferred.reject(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
}());
|
||||
|
||||
return promise.all(outstanding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
|
||||
* allows the lazy getter to be defined on a prototype and work correctly with
|
||||
* instances.
|
||||
*
|
||||
* @param Object aObject
|
||||
* The prototype object to define the lazy getter on.
|
||||
* @param String aKey
|
||||
* The key to define the lazy getter on.
|
||||
* @param Function aCallback
|
||||
* The callback that will be called to determine the value. Will be
|
||||
* called with the |this| value of the current instance.
|
||||
*/
|
||||
DevToolsUtils.defineLazyPrototypeGetter =
|
||||
function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
|
||||
Object.defineProperty(aObject, aKey, {
|
||||
configurable: true,
|
||||
get: function() {
|
||||
const value = aCallback.call(this);
|
||||
|
||||
Object.defineProperty(this, aKey, {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: value
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get the property value from a Debugger.Object for a given key. Walks
|
||||
* the prototype chain until the property is found.
|
||||
*
|
||||
* @param Debugger.Object aObject
|
||||
* The Debugger.Object to get the value from.
|
||||
* @param String aKey
|
||||
* The key to look for.
|
||||
* @return Any
|
||||
*/
|
||||
DevToolsUtils.getProperty = function getProperty(aObj, aKey) {
|
||||
let root = aObj;
|
||||
try {
|
||||
do {
|
||||
const desc = aObj.getOwnPropertyDescriptor(aKey);
|
||||
if (desc) {
|
||||
if ("value" in desc) {
|
||||
return desc.value;
|
||||
}
|
||||
// Call the getter if it's safe.
|
||||
return DevToolsUtils.hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
|
||||
}
|
||||
aObj = aObj.proto;
|
||||
} while (aObj);
|
||||
} catch (e) {
|
||||
// If anything goes wrong report the error and return undefined.
|
||||
DevToolsUtils.reportException("getProperty", e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a descriptor has a getter which doesn't call into JavaScript.
|
||||
*
|
||||
* @param Object aDesc
|
||||
* The descriptor to check for a safe getter.
|
||||
* @return Boolean
|
||||
* Whether a safe getter was found.
|
||||
*/
|
||||
DevToolsUtils.hasSafeGetter = function hasSafeGetter(aDesc) {
|
||||
// Scripted functions that are CCWs will not appear scripted until after
|
||||
// unwrapping.
|
||||
// let fn = aDesc.get.unwrap();
|
||||
let fn = aDesc.get;
|
||||
return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if it is safe to read properties and execute methods from the given JS
|
||||
* object. Safety is defined as being protected from unintended code execution
|
||||
* from content scripts (or cross-compartment code).
|
||||
*
|
||||
* See bugs 945920 and 946752 for discussion.
|
||||
*
|
||||
* @type Object aObj
|
||||
* The object to check.
|
||||
* @return Boolean
|
||||
* True if it is safe to read properties from aObj, or false otherwise.
|
||||
*/
|
||||
DevToolsUtils.isSafeJSObject = function isSafeJSObject(aObj) {
|
||||
return true;
|
||||
// If we are running on a worker thread, Cu is not available. In this case,
|
||||
// we always return false, just to be on the safe side.
|
||||
// if (isWorker) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (Cu.getGlobalForObject(aObj) ==
|
||||
// Cu.getGlobalForObject(DevToolsUtils.isSafeJSObject)) {
|
||||
// return true; // aObj is not a cross-compartment wrapper.
|
||||
// }
|
||||
|
||||
// let principal = Cu.getObjectPrincipal(aObj);
|
||||
// if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
|
||||
// return true; // allow chrome objects
|
||||
// }
|
||||
|
||||
// return Cu.isXrayWrapper(aObj);
|
||||
};
|
||||
|
||||
DevToolsUtils.dumpn = function dumpn(str) {
|
||||
if (DevToolsUtils.dumpn.wantLogging) {
|
||||
dump("DBG-SERVER: " + str + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// We want wantLogging to be writable. The DevToolsUtils object is frozen by the
|
||||
// loader, so define it on dumpn instead.
|
||||
DevToolsUtils.dumpn.wantLogging = false;
|
||||
|
||||
/**
|
||||
* A verbose logger for low-level tracing.
|
||||
*/
|
||||
DevToolsUtils.dumpv = function(msg) {
|
||||
if (DevToolsUtils.dumpv.wantVerbose) {
|
||||
DevToolsUtils.dumpn(msg);
|
||||
}
|
||||
};
|
||||
|
||||
// We want wantLogging to be writable. The DevToolsUtils object is frozen by the
|
||||
// loader, so define it on dumpn instead.
|
||||
DevToolsUtils.dumpv.wantVerbose = false;
|
||||
|
||||
DevToolsUtils.dbg_assert = function dbg_assert(cond, e) {
|
||||
if (!cond) {
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Utility function for updating an object with the properties of
|
||||
* other objects.
|
||||
*
|
||||
* @param aTarget Object
|
||||
* The object being updated.
|
||||
* @param aNewAttrs Object
|
||||
* The rest params are objects to update aTarget with. You
|
||||
* can pass as many as you like.
|
||||
*/
|
||||
DevToolsUtils.update = function update(aTarget, ...aArgs) {
|
||||
for (let attrs of aArgs) {
|
||||
for (let key in attrs) {
|
||||
let desc = Object.getOwnPropertyDescriptor(attrs, key);
|
||||
|
||||
if (desc) {
|
||||
Object.defineProperty(aTarget, key, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for getting the values from an object as an array
|
||||
*
|
||||
* @param aObject Object
|
||||
* The object to iterate over
|
||||
*/
|
||||
DevToolsUtils.values = function values(aObject) {
|
||||
return Object.keys(aObject).map(k => aObject[k]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a getter on a specified object that will be created upon first use.
|
||||
*
|
||||
* @param aObject
|
||||
* The object to define the lazy getter on.
|
||||
* @param aName
|
||||
* The name of the getter to define on aObject.
|
||||
* @param aLambda
|
||||
* A function that returns what the getter should return. This will
|
||||
* only ever be called once.
|
||||
*/
|
||||
DevToolsUtils.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) {
|
||||
Object.defineProperty(aObject, aName, {
|
||||
get: function () {
|
||||
delete aObject[aName];
|
||||
return aObject[aName] = aLambda.apply(aObject);
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a getter on a specified object for a module. The module will not
|
||||
* be imported until first use.
|
||||
*
|
||||
* @param aObject
|
||||
* The object to define the lazy getter on.
|
||||
* @param aName
|
||||
* The name of the getter to define on aObject for the module.
|
||||
* @param aResource
|
||||
* The URL used to obtain the module.
|
||||
* @param aSymbol
|
||||
* The name of the symbol exported by the module.
|
||||
* This parameter is optional and defaults to aName.
|
||||
*/
|
||||
DevToolsUtils.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName,
|
||||
aResource,
|
||||
aSymbol)
|
||||
{
|
||||
this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
|
||||
var temp = {};
|
||||
Cu.import(aResource, temp);
|
||||
return temp[aSymbol || aName];
|
||||
});
|
||||
};
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
|
||||
return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
|
||||
});
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "OS", () => {
|
||||
return Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
|
||||
});
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "TextDecoder", () => {
|
||||
return Cu.import("resource://gre/modules/osfile.jsm", {}).TextDecoder;
|
||||
});
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "NetworkHelper", () => {
|
||||
return require("devtools/toolkit/webconsole/network-helper");
|
||||
});
|
||||
|
||||
/**
|
||||
* Performs a request to load the desired URL and returns a promise.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL we will request.
|
||||
* @param aOptions Object
|
||||
* An object with the following optional properties:
|
||||
* - loadFromCache: if false, will bypass the cache and
|
||||
* always load fresh from the network (default: true)
|
||||
* - policy: the nsIContentPolicy type to apply when fetching the URL
|
||||
* - window: the window to get the loadGroup from
|
||||
* - charset: the charset to use if the channel doesn't provide one
|
||||
* @returns Promise that resolves with an object with the following members on
|
||||
* success:
|
||||
* - content: the document at that URL, as a string,
|
||||
* - contentType: the content type of the document
|
||||
*
|
||||
* If an error occurs, the promise is rejected with that error.
|
||||
*
|
||||
* XXX: It may be better to use nsITraceableChannel to get to the sources
|
||||
* without relying on caching when we can (not for eval, etc.):
|
||||
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
|
||||
*/
|
||||
function mainThreadFetch(aURL, aOptions={ loadFromCache: true,
|
||||
policy: Ci.nsIContentPolicy.TYPE_OTHER,
|
||||
window: null,
|
||||
charset: null }) {
|
||||
// Create a channel.
|
||||
let url = aURL.split(" -> ").pop();
|
||||
let channel;
|
||||
try {
|
||||
channel = newChannelForURL(url, aOptions);
|
||||
} catch (ex) {
|
||||
return promise.reject(ex);
|
||||
}
|
||||
|
||||
// Set the channel options.
|
||||
channel.loadFlags = aOptions.loadFromCache
|
||||
? channel.LOAD_FROM_CACHE
|
||||
: channel.LOAD_BYPASS_CACHE;
|
||||
|
||||
if (aOptions.window) {
|
||||
// Respect private browsing.
|
||||
channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocumentLoader)
|
||||
.loadGroup;
|
||||
}
|
||||
|
||||
let deferred = promise.defer();
|
||||
let onResponse = (stream, status, request) => {
|
||||
if (!components.isSuccessCode(status)) {
|
||||
deferred.reject(new Error('Failed to fetch ${url}. Code ${status}.'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// We cannot use NetUtil to do the charset conversion as if charset
|
||||
// information is not available and our default guess is wrong the method
|
||||
// might fail and we lose the stream data. This means we can't fall back
|
||||
// to using the locale default encoding (bug 1181345).
|
||||
|
||||
// Read and decode the data according to the locale default encoding.
|
||||
let available = stream.available();
|
||||
let source = NetUtil.readInputStreamToString(stream, available);
|
||||
stream.close();
|
||||
|
||||
// If the channel or the caller has correct charset information, the
|
||||
// content will be decoded correctly. If we have to fall back to UTF-8 and
|
||||
// the guess is wrong, the conversion fails and convertToUnicode returns
|
||||
// the input unmodified. Essentially we try to decode the data as UTF-8
|
||||
// and if that fails, we use the locale specific default encoding. This is
|
||||
// the best we can do if the source does not provide charset info.
|
||||
let charset = channel.contentCharset || aOptions.charset || "UTF-8";
|
||||
let unicodeSource = NetworkHelper.convertToUnicode(source, charset);
|
||||
|
||||
deferred.resolve({
|
||||
content: unicodeSource,
|
||||
contentType: request.contentType
|
||||
});
|
||||
} catch (ex) {
|
||||
let uri = request.originalURI;
|
||||
if (ex.name === "NS_BASE_STREAM_CLOSED" && uri instanceof Ci.nsIFileURL) {
|
||||
// Empty files cause NS_BASE_STREAM_CLOSED exception. Use OS.File to
|
||||
// differentiate between empty files and other errors (bug 1170864).
|
||||
// This can be removed when bug 982654 is fixed.
|
||||
|
||||
uri.QueryInterface(Ci.nsIFileURL);
|
||||
let result = OS.File.read(uri.file.path).then(bytes => {
|
||||
// Convert the bytearray to a String.
|
||||
let decoder = new TextDecoder();
|
||||
let content = decoder.decode(bytes);
|
||||
|
||||
// We can't detect the contentType without opening a channel
|
||||
// and that failed already. This is the best we can do here.
|
||||
return {
|
||||
content,
|
||||
contentType: "text/plain"
|
||||
};
|
||||
});
|
||||
|
||||
deferred.resolve(result);
|
||||
} else {
|
||||
deferred.reject(ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Open the channel
|
||||
try {
|
||||
NetUtil.asyncFetch(channel, onResponse);
|
||||
} catch (ex) {
|
||||
return promise.reject(ex);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
|
||||
*
|
||||
* @param {String} url - The URL to open a channel for.
|
||||
* @param {Object} options - The options object passed to @method fetch.
|
||||
* @return {nsIChannel} - The newly created channel. Throws on failure.
|
||||
*/
|
||||
function newChannelForURL(url, { policy }) {
|
||||
let channelOptions = {
|
||||
contentPolicyType: policy,
|
||||
loadUsingSystemPrincipal: true,
|
||||
uri: url
|
||||
};
|
||||
|
||||
try {
|
||||
return NetUtil.newChannel(channelOptions);
|
||||
} catch (e) {
|
||||
// In the xpcshell tests, the script url is the absolute path of the test
|
||||
// file, which will make a malformed URI error be thrown. Add the file
|
||||
// scheme to see if it helps.
|
||||
channelOptions.uri = "file://" + url;
|
||||
|
||||
return NetUtil.newChannel(channelOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch is defined differently depending on whether we are on the main thread
|
||||
// or a worker thread.
|
||||
if (!this.isWorker) {
|
||||
DevToolsUtils.fetch = mainThreadFetch;
|
||||
} else {
|
||||
// Services is not available in worker threads, nor is there any other way
|
||||
// to fetch a URL. We need to enlist the help from the main thread here, by
|
||||
// issuing an rpc request, to fetch the URL on our behalf.
|
||||
DevToolsUtils.fetch = function (url, options) {
|
||||
return rpc("fetch", url, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that is resolved or rejected when all promises have settled
|
||||
* (resolved or rejected).
|
||||
*
|
||||
* This differs from Promise.all, which will reject immediately after the first
|
||||
* rejection, instead of waiting for the remaining promises to settle.
|
||||
*
|
||||
* @param values
|
||||
* Iterable of promises that may be pending, resolved, or rejected. When
|
||||
* when all promises have settled (resolved or rejected), the returned
|
||||
* promise will be resolved or rejected as well.
|
||||
*
|
||||
* @return A new promise that is fulfilled when all values have settled
|
||||
* (resolved or rejected). Its resolution value will be an array of all
|
||||
* resolved values in the given order, or undefined if values is an
|
||||
* empty array. The reject reason will be forwarded from the first
|
||||
* promise in the list of given promises to be rejected.
|
||||
*/
|
||||
DevToolsUtils.settleAll = values => {
|
||||
if (values === null || typeof(values[Symbol.iterator]) != "function") {
|
||||
throw new Error("settleAll() expects an iterable.");
|
||||
}
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
values = Array.isArray(values) ? values : [...values];
|
||||
let countdown = values.length;
|
||||
let resolutionValues = new Array(countdown);
|
||||
let rejectionValue;
|
||||
let rejectionOccurred = false;
|
||||
|
||||
if (!countdown) {
|
||||
deferred.resolve(resolutionValues);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function checkForCompletion() {
|
||||
if (--countdown > 0) {
|
||||
return;
|
||||
}
|
||||
if (!rejectionOccurred) {
|
||||
deferred.resolve(resolutionValues);
|
||||
} else {
|
||||
deferred.reject(rejectionValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
let index = i;
|
||||
let value = values[i];
|
||||
let resolver = result => {
|
||||
resolutionValues[index] = result;
|
||||
checkForCompletion();
|
||||
};
|
||||
let rejecter = error => {
|
||||
if (!rejectionOccurred) {
|
||||
rejectionValue = error;
|
||||
rejectionOccurred = true;
|
||||
}
|
||||
checkForCompletion();
|
||||
};
|
||||
|
||||
if (value && typeof(value.then) == "function") {
|
||||
value.then(resolver, rejecter);
|
||||
} else {
|
||||
// Given value is not a promise, forward it as a resolution value.
|
||||
resolver(value);
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the testing flag is set, various behaviors may be altered from
|
||||
* production mode, typically to enable easier testing or enhanced debugging.
|
||||
*/
|
||||
var testing = false;
|
||||
Object.defineProperty(DevToolsUtils, "testing", {
|
||||
get: function() {
|
||||
return testing;
|
||||
},
|
||||
set: function(state) {
|
||||
testing = state;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the file at the given path for reading.
|
||||
*
|
||||
* @param {String} filePath
|
||||
*
|
||||
* @returns Promise<nsIInputStream>
|
||||
*/
|
||||
DevToolsUtils.openFileStream = function (filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const uri = NetUtil.newURI(new FileUtils.File(filePath));
|
||||
NetUtil.asyncFetch(
|
||||
{ uri, loadUsingSystemPrincipal: true },
|
||||
(stream, result) => {
|
||||
if (!components.isSuccessCode(result)) {
|
||||
reject(new Error('Could not open "${filePath}": result = ${result}'));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stream);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
/**
|
||||
* An OriginalLocation represents a location in an original source.
|
||||
*
|
||||
* @param SourceActor actor
|
||||
* A SourceActor representing an original source.
|
||||
* @param Number line
|
||||
* A line within the given source.
|
||||
* @param Number column
|
||||
* A column within the given line.
|
||||
* @param String name
|
||||
* The name of the symbol corresponding to this OriginalLocation.
|
||||
*/
|
||||
|
||||
function OriginalLocation(actor, line, column, name) {
|
||||
this._connection = actor ? actor.conn : null;
|
||||
this._actorID = actor ? actor.actorID : undefined;
|
||||
this._line = line;
|
||||
this._column = column;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
OriginalLocation.fromGeneratedLocation = function (generatedLocation) {
|
||||
return new OriginalLocation(
|
||||
generatedLocation.generatedSourceActor,
|
||||
generatedLocation.generatedLine,
|
||||
generatedLocation.generatedColumn
|
||||
);
|
||||
};
|
||||
|
||||
OriginalLocation.prototype = {
|
||||
get originalSourceActor() {
|
||||
return this._connection ? this._connection.getActor(this._actorID) : null;
|
||||
},
|
||||
|
||||
get originalUrl() {
|
||||
let actor = this.originalSourceActor;
|
||||
let source = actor.source;
|
||||
return source ? source.url : actor._originalUrl;
|
||||
},
|
||||
|
||||
get originalLine() {
|
||||
return this._line;
|
||||
},
|
||||
|
||||
get originalColumn() {
|
||||
return this._column;
|
||||
},
|
||||
|
||||
get originalName() {
|
||||
return this._name;
|
||||
},
|
||||
|
||||
get generatedSourceActor() {
|
||||
throw new Error("Shouldn't access generatedSourceActor from an OriginalLocation");
|
||||
},
|
||||
|
||||
get generatedLine() {
|
||||
throw new Error("Shouldn't access generatedLine from an OriginalLocation");
|
||||
},
|
||||
|
||||
get generatedColumn() {
|
||||
throw new Error("Shouldn't access generatedColumn from an Originallocation");
|
||||
},
|
||||
|
||||
equals: function (other) {
|
||||
return this.originalSourceActor.url == other.originalSourceActor.url &&
|
||||
this.originalLine === other.originalLine &&
|
||||
(this.originalColumn === undefined ||
|
||||
other.originalColumn === undefined ||
|
||||
this.originalColumn === other.originalColumn);
|
||||
},
|
||||
|
||||
toJSON: function () {
|
||||
return {
|
||||
source: this.originalSourceActor.form(),
|
||||
line: this.originalLine,
|
||||
column: this.originalColumn
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A GeneratedLocation represents a location in a generated source.
|
||||
*
|
||||
* @param SourceActor actor
|
||||
* A SourceActor representing a generated source.
|
||||
* @param Number line
|
||||
* A line within the given source.
|
||||
* @param Number column
|
||||
* A column within the given line.
|
||||
*/
|
||||
function GeneratedLocation(actor, line, column, lastColumn) {
|
||||
this._connection = actor ? actor.conn : null;
|
||||
this._actorID = actor ? actor.actorID : undefined;
|
||||
this._line = line;
|
||||
this._column = column;
|
||||
this._lastColumn = (lastColumn !== undefined) ? lastColumn : column + 1;
|
||||
}
|
||||
|
||||
GeneratedLocation.fromOriginalLocation = function (originalLocation) {
|
||||
return new GeneratedLocation(
|
||||
originalLocation.originalSourceActor,
|
||||
originalLocation.originalLine,
|
||||
originalLocation.originalColumn
|
||||
);
|
||||
};
|
||||
|
||||
GeneratedLocation.prototype = {
|
||||
get originalSourceActor() {
|
||||
throw new Error();
|
||||
},
|
||||
|
||||
get originalUrl() {
|
||||
throw new Error("Shouldn't access originalUrl from a GeneratedLocation");
|
||||
},
|
||||
|
||||
get originalLine() {
|
||||
throw new Error("Shouldn't access originalLine from a GeneratedLocation");
|
||||
},
|
||||
|
||||
get originalColumn() {
|
||||
throw new Error("Shouldn't access originalColumn from a GeneratedLocation");
|
||||
},
|
||||
|
||||
get originalName() {
|
||||
throw new Error("Shouldn't access originalName from a GeneratedLocation");
|
||||
},
|
||||
|
||||
get generatedSourceActor() {
|
||||
return this._connection ? this._connection.getActor(this._actorID) : null;
|
||||
},
|
||||
|
||||
get generatedLine() {
|
||||
return this._line;
|
||||
},
|
||||
|
||||
get generatedColumn() {
|
||||
return this._column;
|
||||
},
|
||||
|
||||
get generatedLastColumn() {
|
||||
return this._lastColumn;
|
||||
},
|
||||
|
||||
equals: function (other) {
|
||||
return this.generatedSourceActor.url == other.generatedSourceActor.url &&
|
||||
this.generatedLine === other.generatedLine &&
|
||||
(this.generatedColumn === undefined ||
|
||||
other.generatedColumn === undefined ||
|
||||
this.generatedColumn === other.generatedColumn);
|
||||
},
|
||||
|
||||
toJSON: function () {
|
||||
return {
|
||||
source: this.generatedSourceActor.form(),
|
||||
line: this.generatedLine,
|
||||
column: this.generatedColumn,
|
||||
lastColumn: this.generatedLastColumn
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
|
||||
let bestOffsetMapping = null;
|
||||
for (let offsetMapping of aScript.getAllColumnOffsets()) {
|
||||
if (!bestOffsetMapping ||
|
||||
(offsetMapping.offset <= aOffset &&
|
||||
offsetMapping.offset > bestOffsetMapping.offset)) {
|
||||
bestOffsetMapping = offsetMapping;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bestOffsetMapping) {
|
||||
// XXX: Try not to completely break the experience of using the debugger for
|
||||
// the user by assuming column 0. Simultaneously, report the error so that
|
||||
// there is a paper trail if the assumption is bad and the debugging
|
||||
// experience becomes wonky.
|
||||
reportError(new Error("Could not find a column for offset " + aOffset
|
||||
+ " in the script " + aScript));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bestOffsetMapping.columnNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!prefix && typeof aActor == "function") {
|
||||
// typeName is a convention used with protocol.js-based actors
|
||||
prefix = aActor.prototype.actorPrefix || aActor.prototype.typeName;
|
||||
}
|
||||
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] || undefined;
|
||||
},
|
||||
|
||||
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 (let id in this._cleanups) {
|
||||
this._cleanups[id].disconnect();
|
||||
}
|
||||
this._cleanups = {};
|
||||
},
|
||||
|
||||
forEach: function(callback) {
|
||||
for (let name in this._actors) {
|
||||
callback(this._actors[name]);
|
||||
}
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,468 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 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";
|
||||
|
||||
// const { Cc, Ci, Cu } = require("chrome");
|
||||
// const Services = require("Services");
|
||||
// const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
|
||||
// const { DebuggerServer } = require("devtools/server/main");
|
||||
|
||||
// loader.lazyGetter(this, "ppmm", () => {
|
||||
// return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster);
|
||||
// });
|
||||
|
||||
/* Root actor for the remote debugging protocol. */
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* - addonList: a live list (see below) of addon actors. If present, the
|
||||
* new root actor supports the 'listAddons' request, providing the live
|
||||
* list's elements as its addon actors, and sending 'addonListchanged'
|
||||
* notifications when the live list's contents change.
|
||||
*
|
||||
* - 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:
|
||||
*
|
||||
* - getList: a method that returns a promise to the contents of the list.
|
||||
*
|
||||
* - 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._onAddonListChanged = this.onAddonListChanged.bind(this);
|
||||
this._extraActors = {};
|
||||
|
||||
this._globalActorPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._globalActorPool);
|
||||
|
||||
this._chromeActor = null;
|
||||
}
|
||||
|
||||
RootActor.prototype = {
|
||||
constructor: RootActor,
|
||||
applicationType: "browser",
|
||||
|
||||
traits: {
|
||||
sources: true,
|
||||
// Whether the inspector actor allows modifying outer HTML.
|
||||
editOuterHTML: true,
|
||||
// Whether the inspector actor allows modifying innerHTML and inserting
|
||||
// adjacent HTML.
|
||||
pasteHTML: true,
|
||||
// Whether the server-side highlighter actor exists and can be used to
|
||||
// remotely highlight nodes (see server/actors/highlighters.js)
|
||||
highlightable: true,
|
||||
// Which custom highlighter does the server-side highlighter actor supports?
|
||||
// (see server/actors/highlighters.js)
|
||||
customHighlighters: true,
|
||||
// Whether the inspector actor implements the getImageDataFromURL
|
||||
// method that returns data-uris for image URLs. This is used for image
|
||||
// tooltips for instance
|
||||
urlToImageDataResolver: true,
|
||||
networkMonitor: true,
|
||||
// Whether the storage inspector actor to inspect cookies, etc.
|
||||
storageInspector: true,
|
||||
// Whether storage inspector is read only
|
||||
storageInspectorReadOnly: true,
|
||||
// Whether conditional breakpoints are supported
|
||||
conditionalBreakpoints: true,
|
||||
// Whether the server supports full source actors (breakpoints on
|
||||
// eval scripts, etc)
|
||||
debuggerSourceActors: true,
|
||||
bulk: true,
|
||||
// Whether the style rule actor implements the modifySelector method
|
||||
// that modifies the rule's selector
|
||||
selectorEditable: true,
|
||||
// Whether the page style actor implements the addNewRule method that
|
||||
// adds new rules to the page
|
||||
addNewRule: true,
|
||||
// Whether the dom node actor implements the getUniqueSelector method
|
||||
getUniqueSelector: true,
|
||||
// Whether the director scripts are supported
|
||||
directorScripts: true,
|
||||
// Whether the debugger server supports
|
||||
// blackboxing/pretty-printing (not supported in Fever Dream yet)
|
||||
noBlackBoxing: false,
|
||||
noPrettyPrinting: false,
|
||||
// Whether the page style actor implements the getUsedFontFaces method
|
||||
// that returns the font faces used on a node
|
||||
getUsedFontFaces: true,
|
||||
// Trait added in Gecko 38, indicating that all features necessary for
|
||||
// grabbing allocations from the MemoryActor are available for the performance tool
|
||||
memoryActorAllocations: true,
|
||||
// Added in Gecko 40, indicating that the backend isn't stupid about
|
||||
// sending resumption packets on tab navigation.
|
||||
noNeedToFakeResumptionOnNavigation: true,
|
||||
// Added in Firefox 40. Indicates that the backend supports registering custom
|
||||
// commands through the WebConsoleCommands API.
|
||||
webConsoleCommands: true,
|
||||
// Whether root actor exposes tab actors
|
||||
// if allowChromeProcess is true, you can fetch a ChromeActor instance
|
||||
// to debug chrome and any non-content ressource via getProcess request
|
||||
// if allocChromeProcess is defined, but not true, it means that root actor
|
||||
// no longer expose tab actors, but also that getProcess forbids
|
||||
// exposing actors for security reasons
|
||||
get allowChromeProcess() {
|
||||
return DebuggerServer.allowChromeProcess;
|
||||
},
|
||||
// Whether or not `getProfile()` supports specifying a `startTime`
|
||||
// and `endTime` to filter out samples. Fx40+
|
||||
profilerDataFilterable: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
|
||||
*/
|
||||
sayHello: function() {
|
||||
return {
|
||||
from: this.actorID,
|
||||
applicationType: this.applicationType,
|
||||
/* This is not in the spec, but it's used by tests. */
|
||||
testConnectionPrefix: this.conn.prefix,
|
||||
traits: this.traits
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 (this._parameters.addonList) {
|
||||
this._parameters.addonList.onListChanged = null;
|
||||
}
|
||||
if (typeof this._parameters.onShutdown === 'function') {
|
||||
this._parameters.onShutdown();
|
||||
}
|
||||
this._extraActors = null;
|
||||
this.conn = null;
|
||||
this._tabActorPool = null;
|
||||
this._globalActorPool = null;
|
||||
this._parameters = null;
|
||||
this._chromeActor = 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: this.actorID, 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;
|
||||
return tabList.getList().then((tabActors) => {
|
||||
for (let tabActor of tabActors) {
|
||||
if (tabActor.selected) {
|
||||
selected = tabActorList.length;
|
||||
}
|
||||
tabActor.parentID = this.actorID;
|
||||
newActorPool.addActor(tabActor);
|
||||
tabActorList.push(tabActor);
|
||||
}
|
||||
/* DebuggerServer.addGlobalActor support: create actors. */
|
||||
if (!this._globalActorPool) {
|
||||
this._globalActorPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._globalActorPool);
|
||||
}
|
||||
// this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
|
||||
/*
|
||||
* 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": this.actorID,
|
||||
"selected": selected || 0,
|
||||
"tabs": tabActorList.map(actor => actor.form())
|
||||
};
|
||||
|
||||
/* If a root window is accessible, include its URL. */
|
||||
if (this.url) {
|
||||
reply.url = this.url;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
});
|
||||
},
|
||||
|
||||
onGetTab: function (options) {
|
||||
let tabList = this._parameters.tabList;
|
||||
if (!tabList) {
|
||||
return { error: "noTabs",
|
||||
message: "This root actor has no browser tabs." };
|
||||
}
|
||||
if (!this._tabActorPool) {
|
||||
this._tabActorPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._tabActorPool);
|
||||
}
|
||||
return tabList.getTab(options)
|
||||
.then(tabActor => {
|
||||
tabActor.parentID = this.actorID;
|
||||
this._tabActorPool.addActor(tabActor);
|
||||
|
||||
return { tab: tabActor.form() };
|
||||
}, error => {
|
||||
if (error.error) {
|
||||
// Pipe expected errors as-is to the client
|
||||
return error;
|
||||
} else {
|
||||
return { error: "noTab",
|
||||
message: "Unexpected error while calling getTab(): " + error };
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onTabListChanged: function () {
|
||||
this.conn.send({ from: this.actorID, type:"tabListChanged" });
|
||||
/* It's a one-shot notification; no need to watch any more. */
|
||||
this._parameters.tabList.onListChanged = null;
|
||||
},
|
||||
|
||||
onListAddons: function () {
|
||||
let addonList = this._parameters.addonList;
|
||||
if (!addonList) {
|
||||
return { from: this.actorID, error: "noAddons",
|
||||
message: "This root actor has no browser addons." };
|
||||
}
|
||||
|
||||
return addonList.getList().then((addonActors) => {
|
||||
let addonActorPool = new ActorPool(this.conn);
|
||||
for (let addonActor of addonActors) {
|
||||
addonActorPool.addActor(addonActor);
|
||||
}
|
||||
|
||||
if (this._addonActorPool) {
|
||||
this.conn.removeActorPool(this._addonActorPool);
|
||||
}
|
||||
this._addonActorPool = addonActorPool;
|
||||
this.conn.addActorPool(this._addonActorPool);
|
||||
|
||||
addonList.onListChanged = this._onAddonListChanged;
|
||||
|
||||
return {
|
||||
"from": this.actorID,
|
||||
"addons": addonActors.map(addonActor => addonActor.form())
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onAddonListChanged: function () {
|
||||
this.conn.send({ from: this.actorID, type: "addonListChanged" });
|
||||
this._parameters.addonList.onListChanged = null;
|
||||
},
|
||||
|
||||
onListProcesses: function () {
|
||||
let processes = [];
|
||||
for (let i = 0; i < ppmm.childCount; i++) {
|
||||
processes.push({
|
||||
id: i, // XXX: may not be a perfect id, but process message manager doesn't expose anything...
|
||||
parent: i == 0, // XXX Weak, but appear to be stable
|
||||
tabCount: undefined, // TODO: exposes process message manager on frameloaders in order to compute this
|
||||
});
|
||||
}
|
||||
return { processes: processes };
|
||||
},
|
||||
|
||||
onGetProcess: function (aRequest) {
|
||||
if (!DebuggerServer.allowChromeProcess) {
|
||||
return { error: "forbidden",
|
||||
message: "You are not allowed to debug chrome." };
|
||||
}
|
||||
if (("id" in aRequest) && typeof(aRequest.id) != "number") {
|
||||
return { error: "wrongParameter",
|
||||
message: "getProcess requires a valid `id` attribute." };
|
||||
}
|
||||
// If the request doesn't contains id parameter or id is 0
|
||||
// (id == 0, based on onListProcesses implementation)
|
||||
if ((!("id" in aRequest)) || aRequest.id === 0) {
|
||||
if (!this._chromeActor) {
|
||||
// Create a ChromeActor for the parent process
|
||||
let { ChromeActor } = require("devtools/server/actors/chrome");
|
||||
this._chromeActor = new ChromeActor(this.conn);
|
||||
this._globalActorPool.addActor(this._chromeActor);
|
||||
}
|
||||
|
||||
return { form: this._chromeActor.form() };
|
||||
} else {
|
||||
let mm = ppmm.getChildAt(aRequest.id);
|
||||
if (!mm) {
|
||||
return { error: "noProcess",
|
||||
message: "There is no process with id '" + aRequest.id + "'." };
|
||||
}
|
||||
return DebuggerServer.connectToContent(this.conn, mm)
|
||||
.then(form => ({ form }));
|
||||
}
|
||||
},
|
||||
|
||||
/* This is not in the spec, but it's used by tests. */
|
||||
onEcho: function (aRequest) {
|
||||
/*
|
||||
* Request packets are frozen. Copy aRequest, so that
|
||||
* DebuggerServerConnection.onPacket can attach a 'from' property.
|
||||
*/
|
||||
return Cu.cloneInto(aRequest, {});
|
||||
},
|
||||
|
||||
onProtocolDescription: function () {
|
||||
return require("devtools/server/protocol").dumpProtocolSpec();
|
||||
},
|
||||
|
||||
/* Support for DebuggerServer.addGlobalActor. */
|
||||
// _createExtraActors: createExtraActors,
|
||||
// _appendExtraActors: appendExtraActors,
|
||||
|
||||
/**
|
||||
* Remove the extra actor (added by DebuggerServer.addGlobalActor or
|
||||
* DebuggerServer.addTabActor) name |aName|.
|
||||
*/
|
||||
removeActorByName: function(aName) {
|
||||
if (aName in this._extraActors) {
|
||||
const actor = this._extraActors[aName];
|
||||
if (this._globalActorPool.has(actor)) {
|
||||
this._globalActorPool.removeActor(actor);
|
||||
}
|
||||
if (this._tabActorPool) {
|
||||
// Iterate over TabActor instances to also remove tab actors
|
||||
// created during listTabs for each document.
|
||||
this._tabActorPool.forEach(tab => {
|
||||
tab.removeActorByName(aName);
|
||||
});
|
||||
}
|
||||
delete this._extraActors[aName];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RootActor.prototype.requestTypes = {
|
||||
"listTabs": RootActor.prototype.onListTabs,
|
||||
"getTab": RootActor.prototype.onGetTab,
|
||||
"listAddons": RootActor.prototype.onListAddons,
|
||||
"listProcesses": RootActor.prototype.onListProcesses,
|
||||
"getProcess": RootActor.prototype.onGetProcess,
|
||||
"echo": RootActor.prototype.onEcho,
|
||||
"protocolDescription": RootActor.prototype.onProtocolDescription
|
||||
};
|
||||
|
||||
// exports.RootActor = RootActor;
|
||||
File diff suppressed because it is too large.
Load diff
+243
@@ -0,0 +1,243 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 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";
|
||||
|
||||
const { noop } = DevToolsUtils;
|
||||
|
||||
/**
|
||||
* A `ScriptStore` is a cache of `Debugger.Script` instances. It holds strong
|
||||
* references to the cached scripts to alleviate the GC-sensitivity issues that
|
||||
* plague `Debugger.prototype.findScripts`, but this means that its lifetime
|
||||
* must be managed carefully. It is the `ScriptStore` user's responsibility to
|
||||
* ensure that the `ScriptStore` stays up to date.
|
||||
*
|
||||
* Implementation Notes:
|
||||
*
|
||||
* The ScriptStore's prototype methods are very hot, in general. To help the
|
||||
* JIT, they avoid ES6-isms and higher-order iteration functions, for the most
|
||||
* part. You might be wondering why we don't maintain indices on, say,
|
||||
* Debugger.Source for faster querying, if these methods are so hot. First, the
|
||||
* hottest method is actually just getting all scripts; second, populating the
|
||||
* store becomes prohibitively expensive. So we fall back to linear queries
|
||||
* (which isn't so bad, because Debugger.prototype.findScripts is also linear).
|
||||
*/
|
||||
function ScriptStore() {
|
||||
// Set of every Debugger.Script in the cache.
|
||||
this._scripts = new NoDeleteSet;
|
||||
}
|
||||
|
||||
// module.exports = ScriptStore;
|
||||
|
||||
ScriptStore.prototype = {
|
||||
// Populating a ScriptStore.
|
||||
|
||||
/**
|
||||
* Add one script to the cache.
|
||||
*
|
||||
* @param Debugger.Script script
|
||||
*/
|
||||
addScript: function(script) {
|
||||
this._scripts.add(script);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add many scripts to the cache at once.
|
||||
*
|
||||
* @param Array scripts
|
||||
* The set of Debugger.Scripts to add to the cache.
|
||||
*/
|
||||
addScripts: function(scripts) {
|
||||
for (var i = 0, len = scripts.length; i < len; i++) {
|
||||
this.addScript(scripts[i]);
|
||||
}
|
||||
},
|
||||
|
||||
// Querying a ScriptStore.
|
||||
|
||||
/**
|
||||
* Get all the sources for which we have scripts cached.
|
||||
*
|
||||
* @returns Array of Debugger.Source
|
||||
*/
|
||||
getSources: function() {
|
||||
return [...new Set(this._scripts.items.map(s => s.source))];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all the scripts in the cache.
|
||||
*
|
||||
* @returns read-only Array of Debugger.Script.
|
||||
*
|
||||
* NB: The ScriptStore retains ownership of the returned array, and the
|
||||
* ScriptStore's consumers MUST NOT MODIFY its contents!
|
||||
*/
|
||||
getAllScripts: function() {
|
||||
return this._scripts.items;
|
||||
},
|
||||
|
||||
getScriptsBySourceActor: function(sourceActor) {
|
||||
return sourceActor.source ?
|
||||
this.getScriptsBySource(sourceActor.source) :
|
||||
this.getScriptsByURL(sourceActor._originalUrl);
|
||||
},
|
||||
|
||||
getScriptsBySourceActorAndLine: function(sourceActor, line) {
|
||||
return sourceActor.source ?
|
||||
this.getScriptsBySourceAndLine(sourceActor.source, line) :
|
||||
this.getScriptsByURLAndLine(sourceActor._originalUrl, line);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts produced from the given source.
|
||||
*
|
||||
* @param Debugger.Source source
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsBySource: function(source) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (scripts[i].source === source) {
|
||||
results.push(scripts[i]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts produced from the given source whose source code definition
|
||||
* spans the given line.
|
||||
*
|
||||
* @param Debugger.Source source
|
||||
* @param Number line
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsBySourceAndLine: function(source, line) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var script = scripts[i];
|
||||
if (script.source === source &&
|
||||
script.startLine <= line &&
|
||||
(script.startLine + script.lineCount) > line) {
|
||||
results.push(script);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts defined by a source at the given URL.
|
||||
*
|
||||
* @param String url
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsByURL: function(url) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (scripts[i].url === url) {
|
||||
results.push(scripts[i]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts defined by a source a the given URL and whose source code
|
||||
* definition spans the given line.
|
||||
*
|
||||
* @param String url
|
||||
* @param Number line
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsByURLAndLine: function(url, line) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var script = scripts[i];
|
||||
if (script.url === url &&
|
||||
script.startLine <= line &&
|
||||
(script.startLine + script.lineCount) > line) {
|
||||
results.push(script);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A set which can only grow, and does not support the delete operation.
|
||||
* Provides faster iteration than the native Set by maintaining an array of all
|
||||
* items, in addition to the internal set of all items, which allows direct
|
||||
* iteration (without the iteration protocol and calling into C++, which are
|
||||
* both expensive).
|
||||
*/
|
||||
function NoDeleteSet() {
|
||||
this._set = new Set();
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
NoDeleteSet.prototype = {
|
||||
/**
|
||||
* An array containing every item in the set for convenience and faster
|
||||
* iteration. This is public for reading only, and consumers MUST NOT modify
|
||||
* this array!
|
||||
*/
|
||||
items: null,
|
||||
|
||||
/**
|
||||
* Add an item to the set.
|
||||
*
|
||||
* @param any item
|
||||
*/
|
||||
add: function(item) {
|
||||
if (!this._set.has(item)) {
|
||||
this._set.add(item);
|
||||
this.items.push(item);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if the item is in the set, false otherwise.
|
||||
*
|
||||
* @param any item
|
||||
* @returns Boolean
|
||||
*/
|
||||
has: function(item) {
|
||||
return this._set.has(item);
|
||||
}
|
||||
};
|
||||
+829
@@ -0,0 +1,829 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
/* 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";
|
||||
|
||||
// const { Ci, Cu } = require("chrome");
|
||||
// const Services = require("Services");
|
||||
// const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
// const { dbg_assert, fetch } = DevToolsUtils;
|
||||
// const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
// const { OriginalLocation, GeneratedLocation, getOffsetColumn } = require("devtools/server/actors/common");
|
||||
// const { resolve } = require("promise");
|
||||
|
||||
// loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/script", true);
|
||||
// loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/script", true);
|
||||
// loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
|
||||
// loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
|
||||
|
||||
/**
|
||||
* Manages the sources for a thread. Handles source maps, locations in the
|
||||
* sources, etc for ThreadActors.
|
||||
*/
|
||||
function TabSources(threadActor, allowSourceFn=() => true) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._thread = threadActor;
|
||||
this._useSourceMaps = true;
|
||||
this._autoBlackBox = true;
|
||||
this._anonSourceMapId = 1;
|
||||
this.allowSource = source => {
|
||||
return !isHiddenSource(source) && allowSourceFn(source);
|
||||
}
|
||||
|
||||
this.blackBoxedSources = new Set();
|
||||
this.prettyPrintedSources = new Map();
|
||||
|
||||
// generated Debugger.Source -> promise of SourceMapConsumer
|
||||
this._sourceMaps = new Map();
|
||||
// sourceMapURL -> promise of SourceMapConsumer
|
||||
this._sourceMapCache = Object.create(null);
|
||||
// Debugger.Source -> SourceActor
|
||||
this._sourceActors = new Map();
|
||||
// url -> SourceActor
|
||||
this._sourceMappedSourceActors = Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches strings of the form "foo.min.js" or "foo-min.js", etc. If the regular
|
||||
* expression matches, we can be fairly sure that the source is minified, and
|
||||
* treat it as such.
|
||||
*/
|
||||
const MINIFIED_SOURCE_REGEXP = /\bmin\.js$/;
|
||||
|
||||
TabSources.prototype = {
|
||||
/**
|
||||
* Update preferences and clear out existing sources
|
||||
*/
|
||||
reconfigure: function(options) {
|
||||
if ('useSourceMaps' in options) {
|
||||
this._useSourceMaps = options.useSourceMaps;
|
||||
}
|
||||
|
||||
if ('autoBlackBox' in options) {
|
||||
this._autoBlackBox = options.autoBlackBox;
|
||||
}
|
||||
|
||||
this.reset();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear existing sources so they are recreated on the next access.
|
||||
*
|
||||
* @param Object opts
|
||||
* Specify { sourceMaps: true } if you also want to clear
|
||||
* the source map cache (usually done on reload).
|
||||
*/
|
||||
reset: function(opts={}) {
|
||||
this._sourceActors = new Map();
|
||||
this._sourceMaps = new Map();
|
||||
this._sourceMappedSourceActors = Object.create(null);
|
||||
|
||||
if (opts.sourceMaps) {
|
||||
this._sourceMapCache = Object.create(null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the source actor representing the `source` (or
|
||||
* `originalUrl`), creating one if none exists already. May return
|
||||
* null if the source is disallowed.
|
||||
*
|
||||
* @param Debugger.Source source
|
||||
* The source to make an actor for
|
||||
* @param String originalUrl
|
||||
* The original source URL of a sourcemapped source
|
||||
* @param optional Debguger.Source generatedSource
|
||||
* The generated source that introduced this source via source map,
|
||||
* if any.
|
||||
* @param optional String contentType
|
||||
* The content type of the source, if immediately available.
|
||||
* @returns a SourceActor representing the source or null.
|
||||
*/
|
||||
source: function ({ source, originalUrl, generatedSource,
|
||||
isInlineSource, contentType }) {
|
||||
dbg_assert(source || (originalUrl && generatedSource),
|
||||
"TabSources.prototype.source needs an originalUrl or a source");
|
||||
|
||||
if (source) {
|
||||
// If a source is passed, we are creating an actor for a real
|
||||
// source, which may or may not be sourcemapped.
|
||||
|
||||
if (!this.allowSource(source)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// It's a hack, but inline HTML scripts each have real sources,
|
||||
// but we want to represent all of them as one source as the
|
||||
// HTML page. The actor representing this fake HTML source is
|
||||
// stored in this array, which always has a URL, so check it
|
||||
// first.
|
||||
if (source.url in this._sourceMappedSourceActors) {
|
||||
return this._sourceMappedSourceActors[source.url];
|
||||
}
|
||||
|
||||
if (isInlineSource) {
|
||||
// If it's an inline source, the fake HTML source hasn't been
|
||||
// created yet (would have returned above), so flip this source
|
||||
// into a sourcemapped state by giving it an `originalUrl` which
|
||||
// is the HTML url.
|
||||
originalUrl = source.url;
|
||||
source = null;
|
||||
}
|
||||
else if (this._sourceActors.has(source)) {
|
||||
return this._sourceActors.get(source);
|
||||
}
|
||||
}
|
||||
else if (originalUrl) {
|
||||
// Not all "original" scripts are distinctly separate from the
|
||||
// generated script. Pretty-printed sources have a sourcemap for
|
||||
// themselves, so we need to make sure there a real source
|
||||
// doesn't already exist with this URL.
|
||||
for (let [source, actor] of this._sourceActors) {
|
||||
if (source.url === originalUrl) {
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
|
||||
if (originalUrl in this._sourceMappedSourceActors) {
|
||||
return this._sourceMappedSourceActors[originalUrl];
|
||||
}
|
||||
}
|
||||
|
||||
let actor = new SourceActor({
|
||||
thread: this._thread,
|
||||
source: source,
|
||||
originalUrl: originalUrl,
|
||||
generatedSource: generatedSource,
|
||||
isInlineSource: isInlineSource,
|
||||
contentType: contentType
|
||||
});
|
||||
|
||||
let sourceActorStore = this._thread.sourceActorStore;
|
||||
var id = sourceActorStore.getReusableActorId(source, originalUrl);
|
||||
if (id) {
|
||||
actor.actorID = id;
|
||||
}
|
||||
|
||||
this._thread.threadLifetimePool.addActor(actor);
|
||||
sourceActorStore.setReusableActorId(source, originalUrl, actor.actorID);
|
||||
|
||||
if (this._autoBlackBox && this._isMinifiedURL(actor.url)) {
|
||||
this.blackBox(actor.url);
|
||||
}
|
||||
|
||||
if (source) {
|
||||
this._sourceActors.set(source, actor);
|
||||
}
|
||||
else {
|
||||
this._sourceMappedSourceActors[originalUrl] = actor;
|
||||
}
|
||||
|
||||
this._emitNewSource(actor);
|
||||
return actor;
|
||||
},
|
||||
|
||||
_emitNewSource: function(actor) {
|
||||
if (!actor.source) {
|
||||
// Always notify if we don't have a source because that means
|
||||
// it's something that has been sourcemapped, or it represents
|
||||
// the HTML file that contains inline sources.
|
||||
this.emit('newSource', actor);
|
||||
}
|
||||
else {
|
||||
// If sourcemapping is enabled and a source has sourcemaps, we
|
||||
// create `SourceActor` instances for both the original and
|
||||
// generated sources. The source actors for the generated
|
||||
// sources are only for internal use, however; breakpoints are
|
||||
// managed by these internal actors. We only want to notify the
|
||||
// user of the original sources though, so if the actor has a
|
||||
// `Debugger.Source` instance and a valid source map (meaning
|
||||
// it's a generated source), don't send the notification.
|
||||
this.fetchSourceMap(actor.source).then(map => {
|
||||
if (!map) {
|
||||
this.emit('newSource', actor);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getSourceActor: function(source) {
|
||||
if (source.url in this._sourceMappedSourceActors) {
|
||||
return this._sourceMappedSourceActors[source.url];
|
||||
}
|
||||
|
||||
if (this._sourceActors.has(source)) {
|
||||
return this._sourceActors.get(source);
|
||||
}
|
||||
|
||||
throw new Error('getSource: could not find source actor for ' +
|
||||
(source.url || 'source'));
|
||||
},
|
||||
|
||||
getSourceActorByURL: function(url) {
|
||||
if (url) {
|
||||
for (let [source, actor] of this._sourceActors) {
|
||||
if (source.url === url) {
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
|
||||
if (url in this._sourceMappedSourceActors) {
|
||||
return this._sourceMappedSourceActors[url];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('getSourceByURL: could not find source for ' + url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the URL likely points to a minified resource, false
|
||||
* otherwise.
|
||||
*
|
||||
* @param String aURL
|
||||
* The URL to test.
|
||||
* @returns Boolean
|
||||
*/
|
||||
_isMinifiedURL: function (aURL) {
|
||||
try {
|
||||
let url = Services.io.newURI(aURL, null, null)
|
||||
.QueryInterface(Ci.nsIURL);
|
||||
return MINIFIED_SOURCE_REGEXP.test(url.fileName);
|
||||
} catch (e) {
|
||||
// Not a valid URL so don't try to parse out the filename, just test the
|
||||
// whole thing with the minified source regexp.
|
||||
return MINIFIED_SOURCE_REGEXP.test(aURL);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a source actor representing this source. This ignores
|
||||
* source mapping and always returns an actor representing this real
|
||||
* source. Use `createSourceActors` if you want to respect source maps.
|
||||
*
|
||||
* @param Debugger.Source aSource
|
||||
* The source instance to create an actor for.
|
||||
* @returns SourceActor
|
||||
*/
|
||||
createNonSourceMappedActor: function (aSource) {
|
||||
// Don't use getSourceURL because we don't want to consider the
|
||||
// displayURL property if it's an eval source. We only want to
|
||||
// consider real URLs, otherwise if there is a URL but it's
|
||||
// invalid the code below will not set the content type, and we
|
||||
// will later try to fetch the contents of the URL to figure out
|
||||
// the content type, but it's a made up URL for eval sources.
|
||||
let url = isEvalSource(aSource) ? null : aSource.url;
|
||||
let spec = { source: aSource };
|
||||
|
||||
// XXX bug 915433: We can't rely on Debugger.Source.prototype.text
|
||||
// if the source is an HTML-embedded <script> tag. Since we don't
|
||||
// have an API implemented to detect whether this is the case, we
|
||||
// need to be conservative and only treat valid js files as real
|
||||
// sources. Otherwise, use the `originalUrl` property to treat it
|
||||
// as an HTML source that manages multiple inline sources.
|
||||
|
||||
// Assume the source is inline if the element that introduced it is not a
|
||||
// script element, or does not have a src attribute.
|
||||
let element = aSource.element ? aSource.element.unsafeDereference() : null;
|
||||
if (element && (element.tagName !== "SCRIPT" || !element.hasAttribute("src"))) {
|
||||
spec.isInlineSource = true;
|
||||
} else {
|
||||
if (url) {
|
||||
try {
|
||||
let urlInfo = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
|
||||
if (urlInfo.fileExtension === "xml") {
|
||||
// XUL inline scripts may not correctly have the
|
||||
// `source.element` property, so do a blunt check here if
|
||||
// it's an xml page.
|
||||
spec.isInlineSource = true;
|
||||
}
|
||||
else if (urlInfo.fileExtension === "js") {
|
||||
spec.contentType = "text/javascript";
|
||||
}
|
||||
} catch(ex) {
|
||||
// There are a few special URLs that we know are JavaScript:
|
||||
// inline `javascript:` and code coming from the console
|
||||
if (url.indexOf("javascript:") === 0 || url === 'debugger eval code') {
|
||||
spec.contentType = "text/javascript";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Assume the content is javascript if there's no URL
|
||||
spec.contentType = "text/javascript";
|
||||
}
|
||||
}
|
||||
return this.source(spec);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is an internal function that returns a promise of an array
|
||||
* of source actors representing all the source mapped sources of
|
||||
* `aSource`, or `null` if the source is not sourcemapped or
|
||||
* sourcemapping is disabled. Users should call `createSourceActors`
|
||||
* instead of this.
|
||||
*
|
||||
* @param Debugger.Source aSource
|
||||
* The source instance to create actors for.
|
||||
* @return Promise of an array of source actors
|
||||
*/
|
||||
_createSourceMappedActors: function (aSource) {
|
||||
if (!this._useSourceMaps || !aSource.sourceMapURL) {
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
return this.fetchSourceMap(aSource)
|
||||
.then(map => {
|
||||
if (map) {
|
||||
return map.sources.map(s => {
|
||||
return this.source({ originalUrl: s, generatedSource: aSource });
|
||||
}).filter(isNotNull);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the source actors representing the appropriate sources
|
||||
* of `aSource`. If sourcemapped, returns actors for all of the original
|
||||
* sources, otherwise returns a 1-element array with the actor for
|
||||
* `aSource`.
|
||||
*
|
||||
* @param Debugger.Source aSource
|
||||
* The source instance to create actors for.
|
||||
* @param Promise of an array of source actors
|
||||
*/
|
||||
createSourceActors: function(aSource) {
|
||||
return this._createSourceMappedActors(aSource).then(actors => {
|
||||
let actor = this.createNonSourceMappedActor(aSource);
|
||||
return (actors || [actor]).filter(isNotNull);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a promise of a SourceMapConsumer for the source map for
|
||||
* `aSource`; if we already have such a promise extant, return that.
|
||||
* This will fetch the source map if we don't have a cached object
|
||||
* and source maps are enabled (see `_fetchSourceMap`).
|
||||
*
|
||||
* @param Debugger.Source aSource
|
||||
* The source instance to get sourcemaps for.
|
||||
* @return Promise of a SourceMapConsumer
|
||||
*/
|
||||
fetchSourceMap: function (aSource) {
|
||||
if (this._sourceMaps.has(aSource)) {
|
||||
return this._sourceMaps.get(aSource);
|
||||
}
|
||||
else if (!aSource || !aSource.sourceMapURL) {
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
let sourceMapURL = aSource.sourceMapURL;
|
||||
if (aSource.url) {
|
||||
sourceMapURL = this._normalize(sourceMapURL, aSource.url);
|
||||
}
|
||||
let result = this._fetchSourceMap(sourceMapURL, aSource.url);
|
||||
|
||||
// The promises in `_sourceMaps` must be the exact same instances
|
||||
// as returned by `_fetchSourceMap` for `clearSourceMapCache` to work.
|
||||
this._sourceMaps.set(aSource, result);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a promise of a SourceMapConsumer for the source map for
|
||||
* `aSource`. The resolved result may be null if the source does not
|
||||
* have a source map or source maps are disabled.
|
||||
*/
|
||||
getSourceMap: function(aSource) {
|
||||
return resolve(this._sourceMaps.get(aSource));
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a SourceMapConsumer for the source map for
|
||||
* |aSource|.
|
||||
*/
|
||||
setSourceMap: function(aSource, aMap) {
|
||||
this._sourceMaps.set(aSource, resolve(aMap));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a promise of a SourceMapConsumer for the source map located at
|
||||
* |aAbsSourceMapURL|, which must be absolute. If there is already such a
|
||||
* promise extant, return it. This will not fetch if source maps are
|
||||
* disabled.
|
||||
*
|
||||
* @param string aAbsSourceMapURL
|
||||
* The source map URL, in absolute form, not relative.
|
||||
* @param string aScriptURL
|
||||
* When the source map URL is a data URI, there is no sourceRoot on the
|
||||
* source map, and the source map's sources are relative, we resolve
|
||||
* them from aScriptURL.
|
||||
*/
|
||||
_fetchSourceMap: function (aAbsSourceMapURL, aSourceURL) {
|
||||
if (!this._useSourceMaps) {
|
||||
return resolve(null);
|
||||
}
|
||||
else if (this._sourceMapCache[aAbsSourceMapURL]) {
|
||||
return this._sourceMapCache[aAbsSourceMapURL];
|
||||
}
|
||||
|
||||
let fetching = fetch(aAbsSourceMapURL, { loadFromCache: false })
|
||||
.then(({ content }) => {
|
||||
let map = new SourceMapConsumer(content);
|
||||
this._setSourceMapRoot(map, aAbsSourceMapURL, aSourceURL);
|
||||
return map;
|
||||
})
|
||||
.then(null, error => {
|
||||
if (!DevToolsUtils.reportingDisabled) {
|
||||
DevToolsUtils.reportException("TabSources.prototype._fetchSourceMap", error);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
this._sourceMapCache[aAbsSourceMapURL] = fetching;
|
||||
return fetching;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the source map's sourceRoot to be relative to the source map url.
|
||||
*/
|
||||
_setSourceMapRoot: function (aSourceMap, aAbsSourceMapURL, aScriptURL) {
|
||||
// No need to do this fiddling if we won't be fetching any sources over the
|
||||
// wire.
|
||||
if (aSourceMap.hasContentsOfAllSources()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const base = this._dirname(
|
||||
aAbsSourceMapURL.indexOf("data:") === 0
|
||||
? aScriptURL
|
||||
: aAbsSourceMapURL);
|
||||
aSourceMap.sourceRoot = aSourceMap.sourceRoot
|
||||
? this._normalize(aSourceMap.sourceRoot, base)
|
||||
: base;
|
||||
},
|
||||
|
||||
_dirname: function (aPath) {
|
||||
return Services.io.newURI(
|
||||
".", null, Services.io.newURI(aPath, null, null)).spec;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the source map cache. Source maps are cached by URL so
|
||||
* they can be reused across separate Debugger instances (once in
|
||||
* this cache, they will never be reparsed again). They are
|
||||
* also cached by Debugger.Source objects for usefulness. By default
|
||||
* this just removes the Debugger.Source cache, but you can remove
|
||||
* the lower-level URL cache with the `hard` option.
|
||||
*
|
||||
* @param aSourceMapURL string
|
||||
* The source map URL to uncache
|
||||
* @param opts object
|
||||
* An object with the following properties:
|
||||
* - hard: Also remove the lower-level URL cache, which will
|
||||
* make us completely forget about the source map.
|
||||
*/
|
||||
clearSourceMapCache: function(aSourceMapURL, opts = { hard: false }) {
|
||||
let oldSm = this._sourceMapCache[aSourceMapURL];
|
||||
|
||||
if (opts.hard) {
|
||||
delete this._sourceMapCache[aSourceMapURL];
|
||||
}
|
||||
|
||||
if (oldSm) {
|
||||
// Clear out the current cache so all sources will get the new one
|
||||
for (let [source, sm] of this._sourceMaps.entries()) {
|
||||
if (sm === oldSm) {
|
||||
this._sourceMaps.delete(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Forcefully change the source map of a source, changing the
|
||||
* sourceMapURL and installing the source map in the cache. This is
|
||||
* necessary to expose changes across Debugger instances
|
||||
* (pretty-printing is the use case). Generate a random url if one
|
||||
* isn't specified, allowing you to set "anonymous" source maps.
|
||||
*
|
||||
* @param aSource Debugger.Source
|
||||
* The source to change the sourceMapURL property
|
||||
* @param aUrl string
|
||||
* The source map URL (optional)
|
||||
* @param aMap SourceMapConsumer
|
||||
* The source map instance
|
||||
*/
|
||||
setSourceMapHard: function(aSource, aUrl, aMap) {
|
||||
let url = aUrl;
|
||||
if (!url) {
|
||||
// This is a littly hacky, but we want to forcefully set a
|
||||
// sourcemap regardless of sourcemap settings. We want to
|
||||
// literally change the sourceMapURL so that all debuggers will
|
||||
// get this and pretty-printing will Just Work (Debugger.Source
|
||||
// instances are per-debugger, so we can't key off that). To
|
||||
// avoid tons of work serializing the sourcemap into a data url,
|
||||
// just make a fake URL and stick the sourcemap there.
|
||||
url = "internal://sourcemap" + (this._anonSourceMapId++) + '/';
|
||||
}
|
||||
aSource.sourceMapURL = url;
|
||||
|
||||
// Forcefully set the sourcemap cache. This will be used even if
|
||||
// sourcemaps are disabled.
|
||||
this._sourceMapCache[url] = resolve(aMap);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the non-source-mapped location of the given Debugger.Frame. If the
|
||||
* frame does not have a script, the location's properties are all null.
|
||||
*
|
||||
* @param Debugger.Frame aFrame
|
||||
* The frame whose location we are getting.
|
||||
* @returns Object
|
||||
* Returns an object of the form { source, line, column }
|
||||
*/
|
||||
getFrameLocation: function (aFrame) {
|
||||
if (!aFrame || !aFrame.script) {
|
||||
return new GeneratedLocation();
|
||||
}
|
||||
|
||||
return new GeneratedLocation(
|
||||
this.createNonSourceMappedActor(aFrame.script.source),
|
||||
aFrame.script.getOffsetLine(aFrame.offset),
|
||||
getOffsetColumn(aFrame.offset, aFrame.script)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise of the location in the original source if the source is
|
||||
* source mapped, otherwise a promise of the same location. This can
|
||||
* be called with a source from *any* Debugger instance and we make
|
||||
* sure to that it works properly, reusing source maps if already
|
||||
* fetched. Use this from any actor that needs sourcemapping.
|
||||
*/
|
||||
getOriginalLocation: function (generatedLocation) {
|
||||
let {
|
||||
generatedSourceActor,
|
||||
generatedLine,
|
||||
generatedColumn
|
||||
} = generatedLocation;
|
||||
let source = generatedSourceActor.source;
|
||||
let url = source ? source.url : generatedSourceActor._originalUrl;
|
||||
|
||||
// In certain scenarios the source map may have not been fetched
|
||||
// yet (or at least tied to this Debugger.Source instance), so use
|
||||
// `fetchSourceMap` instead of `getSourceMap`. This allows this
|
||||
// function to be called from anywere (across debuggers) and it
|
||||
// should just automatically work.
|
||||
return this.fetchSourceMap(source).then(map => {
|
||||
if (map) {
|
||||
let {
|
||||
source: originalUrl,
|
||||
line: originalLine,
|
||||
column: originalColumn,
|
||||
name: originalName
|
||||
} = map.originalPositionFor({
|
||||
line: generatedLine,
|
||||
column: generatedColumn == null ? Infinity : generatedColumn
|
||||
});
|
||||
|
||||
// Since the `Debugger.Source` instance may come from a
|
||||
// different `Debugger` instance (any actor can call this
|
||||
// method), we can't rely on any of the source discovery
|
||||
// setup (`_discoverSources`, etc) to have been run yet. So
|
||||
// we have to assume that the actor may not already exist,
|
||||
// and we might need to create it, so use `source` and give
|
||||
// it the required parameters for a sourcemapped source.
|
||||
return new OriginalLocation(
|
||||
originalUrl ? this.source({
|
||||
originalUrl: originalUrl,
|
||||
generatedSource: source
|
||||
}) : null,
|
||||
originalLine,
|
||||
originalColumn,
|
||||
originalName
|
||||
);
|
||||
}
|
||||
|
||||
// No source map
|
||||
return OriginalLocation.fromGeneratedLocation(generatedLocation);
|
||||
});
|
||||
},
|
||||
|
||||
getAllGeneratedLocations: function (originalLocation) {
|
||||
let {
|
||||
originalSourceActor,
|
||||
originalLine,
|
||||
originalColumn
|
||||
} = originalLocation;
|
||||
|
||||
let source = originalSourceActor.source ||
|
||||
originalSourceActor.generatedSource;
|
||||
|
||||
return this.fetchSourceMap(source).then((map) => {
|
||||
if (map) {
|
||||
map.computeColumnSpans();
|
||||
|
||||
return map.allGeneratedPositionsFor({
|
||||
source: originalSourceActor.url,
|
||||
line: originalLine,
|
||||
column: originalColumn
|
||||
}).map(({ line, column, lastColumn }) => {
|
||||
return new GeneratedLocation(
|
||||
this.createNonSourceMappedActor(source),
|
||||
line,
|
||||
column,
|
||||
lastColumn
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return [GeneratedLocation.fromOriginalLocation(originalLocation)];
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Returns a promise of the location in the generated source corresponding to
|
||||
* the original source and line given.
|
||||
*
|
||||
* When we pass a script S representing generated code to `sourceMap`,
|
||||
* above, that returns a promise P. The process of resolving P populates
|
||||
* the tables this function uses; thus, it won't know that S's original
|
||||
* source URLs map to S until P is resolved.
|
||||
*/
|
||||
getGeneratedLocation: function (originalLocation) {
|
||||
let { originalSourceActor } = originalLocation;
|
||||
|
||||
// Both original sources and normal sources could have sourcemaps,
|
||||
// because normal sources can be pretty-printed which generates a
|
||||
// sourcemap for itself. Check both of the source properties to make it work
|
||||
// for both kinds of sources.
|
||||
let source = originalSourceActor.source || originalSourceActor.generatedSource;
|
||||
|
||||
// See comment about `fetchSourceMap` in `getOriginalLocation`.
|
||||
return this.fetchSourceMap(source).then((map) => {
|
||||
if (map) {
|
||||
let {
|
||||
originalLine,
|
||||
originalColumn
|
||||
} = originalLocation;
|
||||
|
||||
let {
|
||||
line: generatedLine,
|
||||
column: generatedColumn
|
||||
} = map.generatedPositionFor({
|
||||
source: originalSourceActor.url,
|
||||
line: originalLine,
|
||||
column: originalColumn == null ? 0 : originalColumn,
|
||||
bias: SourceMapConsumer.LEAST_UPPER_BOUND
|
||||
});
|
||||
|
||||
return new GeneratedLocation(
|
||||
this.createNonSourceMappedActor(source),
|
||||
generatedLine,
|
||||
generatedColumn
|
||||
);
|
||||
}
|
||||
|
||||
return GeneratedLocation.fromOriginalLocation(originalLocation);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if URL for the given source is black boxed.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL of the source which we are checking whether it is black
|
||||
* boxed or not.
|
||||
*/
|
||||
isBlackBoxed: function (aURL) {
|
||||
return this.blackBoxedSources.has(aURL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the given source URL to the set of sources that are black boxed.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL of the source which we are black boxing.
|
||||
*/
|
||||
blackBox: function (aURL) {
|
||||
this.blackBoxedSources.add(aURL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the given source URL to the set of sources that are black boxed.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL of the source which we are no longer black boxing.
|
||||
*/
|
||||
unblackBox: function (aURL) {
|
||||
this.blackBoxedSources.delete(aURL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the given URL is pretty printed.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL of the source that might be pretty printed.
|
||||
*/
|
||||
isPrettyPrinted: function (aURL) {
|
||||
return this.prettyPrintedSources.has(aURL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the given URL to the set of sources that are pretty printed.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL of the source to be pretty printed.
|
||||
*/
|
||||
prettyPrint: function (aURL, aIndent) {
|
||||
this.prettyPrintedSources.set(aURL, aIndent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the indent the given URL was pretty printed by.
|
||||
*/
|
||||
prettyPrintIndent: function (aURL) {
|
||||
return this.prettyPrintedSources.get(aURL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the given URL from the set of sources that are pretty printed.
|
||||
*
|
||||
* @param aURL String
|
||||
* The URL of the source that is no longer pretty printed.
|
||||
*/
|
||||
disablePrettyPrint: function (aURL) {
|
||||
this.prettyPrintedSources.delete(aURL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Normalize multiple relative paths towards the base paths on the right.
|
||||
*/
|
||||
_normalize: function (...aURLs) {
|
||||
dbg_assert(aURLs.length > 1, "Should have more than 1 URL");
|
||||
let base = Services.io.newURI(aURLs.pop(), null, null);
|
||||
let url;
|
||||
while ((url = aURLs.pop())) {
|
||||
base = Services.io.newURI(url, null, base);
|
||||
}
|
||||
return base.spec;
|
||||
},
|
||||
|
||||
iter: function () {
|
||||
let actors = Object.keys(this._sourceMappedSourceActors).map(k => {
|
||||
return this._sourceMappedSourceActors[k];
|
||||
});
|
||||
for (let actor of this._sourceActors.values()) {
|
||||
if (!this._sourceMaps.has(actor.source)) {
|
||||
actors.push(actor);
|
||||
}
|
||||
}
|
||||
return actors;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Checks if a source should never be displayed to the user because
|
||||
* it's either internal or we don't support in the UI yet.
|
||||
*/
|
||||
function isHiddenSource(aSource) {
|
||||
// Ignore the internal Function.prototype script
|
||||
return aSource.text === '() {\n}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if its argument is not null.
|
||||
*/
|
||||
function isNotNull(aThing) {
|
||||
return aThing !== null;
|
||||
}
|
||||
|
||||
exports.TabSources = TabSources;
|
||||
exports.isHiddenSource = isHiddenSource;
|
||||
+2156
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,314 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
//;(function(id, factory) { // Module boilerplate :(
|
||||
// if (typeof(define) === 'function') { // RequireJS
|
||||
// define(factory);
|
||||
// } else if (typeof(require) === 'function') { // CommonJS
|
||||
// factory.call(this, require, exports, module);
|
||||
// } else if (String(this).indexOf('BackstagePass') >= 0) { // JSM
|
||||
// this[factory.name] = {};
|
||||
// try {
|
||||
// this.console = this['Components'].utils
|
||||
// .import('resource://gre/modules/devtools/Console.jsm', {}).console;
|
||||
// }
|
||||
// catch (ex) {
|
||||
// // Avoid failures on different toolkit configurations.
|
||||
// }
|
||||
// factory(function require(uri) {
|
||||
// var imports = {};
|
||||
// this['Components'].utils.import(uri, imports);
|
||||
// return imports;
|
||||
// }, this[factory.name], { uri: __URI__, id: id });
|
||||
// this.EXPORTED_SYMBOLS = [factory.name];
|
||||
// } else { // Browser or alike
|
||||
// var globals = this;
|
||||
// factory(function require(id) {
|
||||
// return globals[id];
|
||||
// }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
|
||||
// }
|
||||
//}).call(this, 'promise/core', function Promise(require, exports, module) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var exports = exports || {};
|
||||
|
||||
//module.metadata = {
|
||||
// "stability": "unstable"
|
||||
//};
|
||||
|
||||
/**
|
||||
* Internal utility: Wraps given `value` into simplified promise, successfully
|
||||
* fulfilled to a given `value`. Note the result is not a complete promise
|
||||
* implementation, as its method `then` does not returns anything.
|
||||
*/
|
||||
function fulfilled(value) {
|
||||
return { then: function then(fulfill) { fulfill(value); } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal utility: Wraps given input into simplified promise, pre-rejected
|
||||
* with a given `reason`. Note the result is not a complete promise
|
||||
* implementation, as its method `then` does not returns anything.
|
||||
*/
|
||||
function rejected(reason) {
|
||||
return { then: function then(fulfill, reject) { reject(reason); } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal utility: Returns `true` if given `value` is a promise. Value is
|
||||
* assumed to be a promise if it implements method `then`.
|
||||
*/
|
||||
function isPromise(value) {
|
||||
return value && typeof(value.then) === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates deferred object containing fresh promise & methods to either resolve
|
||||
* or reject it. The result is an object with the following properties:
|
||||
* - `promise` Eventual value representation implementing CommonJS [Promises/A]
|
||||
* (http://wiki.commonjs.org/wiki/Promises/A) API.
|
||||
* - `resolve` Single shot function that resolves enclosed `promise` with a
|
||||
* given `value`.
|
||||
* - `reject` Single shot function that rejects enclosed `promise` with a given
|
||||
* `reason`.
|
||||
*
|
||||
* An optional `prototype` argument is used as a prototype of the returned
|
||||
* `promise` allowing one to implement additional API. If prototype is not
|
||||
* passed then it falls back to `Object.prototype`.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* function fetchURI(uri, type) {
|
||||
* var deferred = defer();
|
||||
* var request = new XMLHttpRequest();
|
||||
* request.open("GET", uri, true);
|
||||
* request.responseType = type;
|
||||
* request.onload = function onload() {
|
||||
* deferred.resolve(request.response);
|
||||
* }
|
||||
* request.onerror = function(event) {
|
||||
* deferred.reject(event);
|
||||
* }
|
||||
* request.send();
|
||||
*
|
||||
* return deferred.promise;
|
||||
* }
|
||||
*/
|
||||
function defer(prototype) {
|
||||
// Define FIFO queue of observer pairs. Once promise is resolved & all queued
|
||||
// observers are forwarded to `result` and variable is set to `null`.
|
||||
var observers = [];
|
||||
|
||||
// Promise `result`, which will be assigned a resolution value once promise
|
||||
// is resolved. Note that result will always be assigned promise (or alike)
|
||||
// object to take care of propagation through promise chains. If result is
|
||||
// `null` promise is not resolved yet.
|
||||
var result = null;
|
||||
|
||||
prototype = (prototype || prototype === null) ? prototype : Object.prototype;
|
||||
|
||||
// Create an object implementing promise API.
|
||||
var promise = Object.create(prototype, {
|
||||
then: { value: function then(onFulfill, onError) {
|
||||
var deferred = defer(prototype);
|
||||
|
||||
function resolve(value) {
|
||||
// If `onFulfill` handler is provided resolve `deferred.promise` with
|
||||
// result of invoking it with a resolution value. If handler is not
|
||||
// provided propagate value through.
|
||||
try {
|
||||
deferred.resolve(onFulfill ? onFulfill(value) : value);
|
||||
}
|
||||
// `onFulfill` may throw exception in which case resulting promise
|
||||
// is rejected with thrown exception.
|
||||
catch(error) {
|
||||
if (exports._reportErrors && typeof(console) === 'object')
|
||||
console.error(error);
|
||||
// Note: Following is equivalent of `deferred.reject(error)`,
|
||||
// we use this shortcut to reduce a stack.
|
||||
deferred.resolve(rejected(error));
|
||||
}
|
||||
}
|
||||
|
||||
function reject(reason) {
|
||||
try {
|
||||
if (onError) deferred.resolve(onError(reason));
|
||||
else deferred.resolve(rejected(reason));
|
||||
}
|
||||
catch(error) {
|
||||
if (exports._reportErrors && typeof(console) === 'object')
|
||||
console.error(error);
|
||||
deferred.resolve(rejected(error));
|
||||
}
|
||||
}
|
||||
|
||||
// If enclosed promise (`this.promise`) observers queue is still alive
|
||||
// enqueue a new observer pair into it. Note that this does not
|
||||
// necessary means that promise is pending, it may already be resolved,
|
||||
// but we still have to queue observers to guarantee an order of
|
||||
// propagation.
|
||||
if (observers) {
|
||||
observers.push({ resolve: resolve, reject: reject });
|
||||
}
|
||||
// Otherwise just forward observer pair right to a `result` promise.
|
||||
else {
|
||||
result.then(resolve, reject);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}}
|
||||
})
|
||||
|
||||
var deferred = {
|
||||
promise: promise,
|
||||
/**
|
||||
* Resolves associated `promise` to a given `value`, unless it's already
|
||||
* resolved or rejected. Note that resolved promise is not necessary a
|
||||
* successfully fulfilled. Promise may be resolved with a promise `value`
|
||||
* in which case `value` promise's fulfillment / rejection will propagate
|
||||
* up to a promise resolved with `value`.
|
||||
*/
|
||||
resolve: function resolve(value) {
|
||||
if (!result) {
|
||||
// Store resolution `value` in a `result` as a promise, so that all
|
||||
// the subsequent handlers can be simply forwarded to it. Since
|
||||
// `result` will be a promise all the value / error propagation will
|
||||
// be uniformly taken care of.
|
||||
result = isPromise(value) ? value : fulfilled(value);
|
||||
|
||||
// Forward already registered observers to a `result` promise in the
|
||||
// order they were registered. Note that we intentionally dequeue
|
||||
// observer at a time until queue is exhausted. This makes sure that
|
||||
// handlers registered as side effect of observer forwarding are
|
||||
// queued instead of being invoked immediately, guaranteeing FIFO
|
||||
// order.
|
||||
while (observers.length) {
|
||||
var observer = observers.shift();
|
||||
result.then(observer.resolve, observer.reject);
|
||||
}
|
||||
|
||||
// Once `observers` queue is exhausted we `null`-ify it, so that
|
||||
// new handlers are forwarded straight to the `result`.
|
||||
observers = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Rejects associated `promise` with a given `reason`, unless it's already
|
||||
* resolved / rejected. This is just a (better performing) convenience
|
||||
* shortcut for `deferred.resolve(reject(reason))`.
|
||||
*/
|
||||
reject: function reject(reason) {
|
||||
// Note that if promise is resolved that does not necessary means that it
|
||||
// is successfully fulfilled. Resolution value may be a promise in which
|
||||
// case its result propagates. In other words if promise `a` is resolved
|
||||
// with promise `b`, `a` is either fulfilled or rejected depending
|
||||
// on weather `b` is fulfilled or rejected. Here `deferred.promise` is
|
||||
// resolved with a promise pre-rejected with a given `reason`, there for
|
||||
// `deferred.promise` is rejected with a given `reason`. This may feel
|
||||
// little awkward first, but doing it this way greatly simplifies
|
||||
// propagation through promise chains.
|
||||
deferred.resolve(rejected(reason));
|
||||
}
|
||||
};
|
||||
|
||||
return deferred;
|
||||
}
|
||||
exports.defer = defer;
|
||||
|
||||
/**
|
||||
* Returns a promise resolved to a given `value`. Optionally a second
|
||||
* `prototype` argument may be provided to be used as a prototype for the
|
||||
* returned promise.
|
||||
*/
|
||||
function resolve(value, prototype) {
|
||||
var deferred = defer(prototype);
|
||||
deferred.resolve(value);
|
||||
return deferred.promise;
|
||||
}
|
||||
exports.resolve = resolve;
|
||||
|
||||
/**
|
||||
* Returns a promise rejected with a given `reason`. Optionally a second
|
||||
* `prototype` argument may be provided to be used as a prototype for the
|
||||
* returned promise.
|
||||
*/
|
||||
function reject(reason, prototype) {
|
||||
var deferred = defer(prototype);
|
||||
deferred.reject(reason);
|
||||
return deferred.promise;
|
||||
}
|
||||
exports.reject = reject;
|
||||
|
||||
var promised = (function() {
|
||||
// Note: Define shortcuts and utility functions here in order to avoid
|
||||
// slower property accesses and unnecessary closure creations on each
|
||||
// call of this popular function.
|
||||
|
||||
var call = Function.call;
|
||||
var concat = Array.prototype.concat;
|
||||
|
||||
// Utility function that does following:
|
||||
// execute([ f, self, args...]) => f.apply(self, args)
|
||||
function execute(args) { return call.apply(call, args) }
|
||||
|
||||
// Utility function that takes promise of `a` array and maybe promise `b`
|
||||
// as arguments and returns promise for `a.concat(b)`.
|
||||
function promisedConcat(promises, unknown) {
|
||||
return promises.then(function(values) {
|
||||
return resolve(unknown).then(function(value) {
|
||||
return values.concat([ value ]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return function promised(f, prototype) {
|
||||
/**
|
||||
Returns a wrapped `f`, which when called returns a promise that resolves to
|
||||
`f(...)` passing all the given arguments to it, which by the way may be
|
||||
promises. Optionally second `prototype` argument may be provided to be used
|
||||
a prototype for a returned promise.
|
||||
|
||||
## Example
|
||||
|
||||
var promise = promised(Array)(1, promise(2), promise(3))
|
||||
promise.then(console.log) // => [ 1, 2, 3 ]
|
||||
**/
|
||||
|
||||
return function promised() {
|
||||
// create array of [ f, this, args... ]
|
||||
return concat.apply([ f, this ], arguments).
|
||||
// reduce it via `promisedConcat` to get promised array of fulfillments
|
||||
reduce(promisedConcat, resolve([], prototype)).
|
||||
// finally map that to promise of `f.apply(this, args...)`
|
||||
then(execute);
|
||||
};
|
||||
}
|
||||
})();
|
||||
exports.promised = promised;
|
||||
//
|
||||
var all = promised(Array);
|
||||
exports.all = all;
|
||||
//
|
||||
//});
|
||||
@@ -0,0 +1,241 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* EventEmitter.
|
||||
*/
|
||||
|
||||
// (function (factory) { // Module boilerplate
|
||||
// if (this.module && module.id.indexOf("event-emitter") >= 0) { // require
|
||||
// factory.call(this, require, exports, module);
|
||||
// } else { // Cu.import
|
||||
// const Cu = Components.utils;
|
||||
// const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
// this.isWorker = false;
|
||||
// this.promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
// factory.call(this, require, this, { exports: this });
|
||||
// this.EXPORTED_SYMBOLS = ["EventEmitter"];
|
||||
// }
|
||||
// }).call(this, function (require, exports, module) {
|
||||
|
||||
this.EventEmitter = function EventEmitter() {};
|
||||
// module.exports = EventEmitter;
|
||||
|
||||
// const { Cu, components } = require("chrome");
|
||||
// const Services = require("Services");
|
||||
// const promise = require("promise");
|
||||
var loggingEnabled = true;
|
||||
|
||||
// if (!isWorker) {
|
||||
// loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
|
||||
// Services.prefs.addObserver("devtools.dump.emit", {
|
||||
// observe: () => {
|
||||
// loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
|
||||
// }
|
||||
// }, false);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Decorate an object with event emitter functionality.
|
||||
*
|
||||
* @param Object aObjectToDecorate
|
||||
* Bind all public methods of EventEmitter to
|
||||
* the aObjectToDecorate object.
|
||||
*/
|
||||
EventEmitter.decorate = function EventEmitter_decorate (aObjectToDecorate) {
|
||||
let emitter = new EventEmitter();
|
||||
aObjectToDecorate.on = emitter.on.bind(emitter);
|
||||
aObjectToDecorate.off = emitter.off.bind(emitter);
|
||||
aObjectToDecorate.once = emitter.once.bind(emitter);
|
||||
aObjectToDecorate.emit = emitter.emit.bind(emitter);
|
||||
};
|
||||
|
||||
EventEmitter.prototype = {
|
||||
/**
|
||||
* Connect a listener.
|
||||
*
|
||||
* @param string aEvent
|
||||
* The event name to which we're connecting.
|
||||
* @param function aListener
|
||||
* Called when the event is fired.
|
||||
*/
|
||||
on: function EventEmitter_on(aEvent, aListener) {
|
||||
if (!this._eventEmitterListeners)
|
||||
this._eventEmitterListeners = new Map();
|
||||
if (!this._eventEmitterListeners.has(aEvent)) {
|
||||
this._eventEmitterListeners.set(aEvent, []);
|
||||
}
|
||||
this._eventEmitterListeners.get(aEvent).push(aListener);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen for the next time an event is fired.
|
||||
*
|
||||
* @param string aEvent
|
||||
* The event name to which we're connecting.
|
||||
* @param function aListener
|
||||
* (Optional) Called when the event is fired. Will be called at most
|
||||
* one time.
|
||||
* @return promise
|
||||
* A promise which is resolved when the event next happens. The
|
||||
* resolution value of the promise is the first event argument. If
|
||||
* you need access to second or subsequent event arguments (it's rare
|
||||
* that this is needed) then use aListener
|
||||
*/
|
||||
once: function EventEmitter_once(aEvent, aListener) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let handler = (aEvent, aFirstArg, ...aRest) => {
|
||||
this.off(aEvent, handler);
|
||||
if (aListener) {
|
||||
aListener.apply(null, [aEvent, aFirstArg, ...aRest]);
|
||||
}
|
||||
deferred.resolve(aFirstArg);
|
||||
};
|
||||
|
||||
handler._originalListener = aListener;
|
||||
this.on(aEvent, handler);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a previously-registered event listener. Works for events
|
||||
* registered with either on or once.
|
||||
*
|
||||
* @param string aEvent
|
||||
* The event name whose listener we're disconnecting.
|
||||
* @param function aListener
|
||||
* The listener to remove.
|
||||
*/
|
||||
off: function EventEmitter_off(aEvent, aListener) {
|
||||
if (!this._eventEmitterListeners)
|
||||
return;
|
||||
let listeners = this._eventEmitterListeners.get(aEvent);
|
||||
if (listeners) {
|
||||
this._eventEmitterListeners.set(aEvent, listeners.filter(l => {
|
||||
return l !== aListener && l._originalListener !== aListener;
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Emit an event. All arguments to this method will
|
||||
* be sent to listener functions.
|
||||
*/
|
||||
emit: function EventEmitter_emit(aEvent) {
|
||||
this.logEvent(aEvent, arguments);
|
||||
|
||||
if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(aEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let originalListeners = this._eventEmitterListeners.get(aEvent);
|
||||
for (let listener of this._eventEmitterListeners.get(aEvent)) {
|
||||
// If the object was destroyed during event emission, stop
|
||||
// emitting.
|
||||
if (!this._eventEmitterListeners) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If listeners were removed during emission, make sure the
|
||||
// event handler we're going to fire wasn't removed.
|
||||
if (originalListeners === this._eventEmitterListeners.get(aEvent) ||
|
||||
this._eventEmitterListeners.get(aEvent).some(l => l === listener)) {
|
||||
try {
|
||||
listener.apply(null, arguments);
|
||||
}
|
||||
catch (ex) {
|
||||
// Prevent a bad listener from interfering with the others.
|
||||
let msg = ex + ": " + ex.stack;
|
||||
Cu.reportError(msg);
|
||||
dump(msg + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
logEvent: function(aEvent, args) {
|
||||
if (!loggingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let caller, func, path;
|
||||
if (!isWorker) {
|
||||
caller = components.stack.caller.caller;
|
||||
func = caller.name;
|
||||
let file = caller.filename;
|
||||
if (file.includes(" -> ")) {
|
||||
file = caller.filename.split(/ -> /)[1];
|
||||
}
|
||||
path = file + ":" + caller.lineNumber;
|
||||
}
|
||||
|
||||
let argOut = "(";
|
||||
if (args.length === 1) {
|
||||
argOut += aEvent;
|
||||
}
|
||||
|
||||
let out = "EMITTING: ";
|
||||
|
||||
// We need this try / catch to prevent any dead object errors.
|
||||
try {
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
if (i === 1) {
|
||||
argOut = "(" + aEvent + ", ";
|
||||
} else {
|
||||
argOut += ", ";
|
||||
}
|
||||
|
||||
let arg = args[i];
|
||||
argOut += arg;
|
||||
|
||||
if (arg && arg.nodeName) {
|
||||
argOut += " (" + arg.nodeName;
|
||||
if (arg.id) {
|
||||
argOut += "#" + arg.id;
|
||||
}
|
||||
if (arg.className) {
|
||||
argOut += "." + arg.className;
|
||||
}
|
||||
argOut += ")";
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
// Object is dead so the toolbox is most likely shutting down,
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
argOut += ")";
|
||||
out += "emit" + argOut + " from " + func + "() -> " + path + "\n";
|
||||
|
||||
dump(out);
|
||||
},
|
||||
};
|
||||
|
||||
// });
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,909 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
|
||||
|
||||
// TODO: Get rid of this code once the marionette server loads transport.js as
|
||||
// an SDK module (see bug 1000814)
|
||||
// (function (factory) { // Module boilerplate
|
||||
// if (this.module && module.id.indexOf("transport") >= 0) { // require
|
||||
// factory.call(this, require, exports);
|
||||
// } else { // loadSubScript
|
||||
// if (this.require) {
|
||||
// factory.call(this, require, this);
|
||||
// } else {
|
||||
// const Cu = Components.utils;
|
||||
// const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
// factory.call(this, require, this);
|
||||
// }
|
||||
// }
|
||||
// }).call(this, function (require, exports) {
|
||||
|
||||
"use strict";
|
||||
|
||||
// const { Cc, Ci, Cr, Cu, CC } = require("chrome");
|
||||
// const Services = require("Services");
|
||||
// const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
// const { dumpn, dumpv } = DevToolsUtils;
|
||||
// const StreamUtils = require("devtools/toolkit/transport/stream-utils");
|
||||
// const { Packet, JSONPacket, BulkPacket } =
|
||||
// require("devtools/toolkit/transport/packets");
|
||||
// const promise = require("promise");
|
||||
// const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
// DevToolsUtils.defineLazyGetter(this, "Pipe", () => {
|
||||
// return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
|
||||
// });
|
||||
|
||||
// DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
|
||||
// return CC("@mozilla.org/scriptableinputstream;1",
|
||||
// "nsIScriptableInputStream", "init");
|
||||
// });
|
||||
|
||||
const PACKET_HEADER_MAX = 200;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* (However, for intra-process connections, LocalDebuggerTransport, below,
|
||||
* is more efficient than using an nsIPipe pair with DebuggerTransport.)
|
||||
*
|
||||
* @param input nsIAsyncInputStream
|
||||
* The input stream.
|
||||
* @param output 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) Call dt.send() / dt.startBulkSend() to send packets.
|
||||
* 4) Call dt.close() to close the connection, and disengage from the event
|
||||
* loop.
|
||||
*
|
||||
* A packet handler is an object with the following 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.
|
||||
*
|
||||
* - onBulkPacket(packet) - called when we have switched to bulk packet
|
||||
* receiving mode. |packet| is an object containing:
|
||||
* * actor: Name of actor that will receive the packet
|
||||
* * type: Name of actor's method that should be called on receipt
|
||||
* * length: Size of the data to be read
|
||||
* * stream: This input stream should only be used directly if you can ensure
|
||||
* that you will read exactly |length| bytes and will not close the
|
||||
* stream when reading is complete
|
||||
* * done: If you use the stream directly (instead of |copyTo| below), you
|
||||
* must signal completion by resolving / rejecting this deferred.
|
||||
* If it's rejected, the transport will be closed. If an Error is
|
||||
* supplied as a rejection value, it will be logged via |dumpn|.
|
||||
* If you do use |copyTo|, resolving is taken care of for you when
|
||||
* copying completes.
|
||||
* * copyTo: A helper function for getting your data out of the stream that
|
||||
* meets the stream handling requirements above, and has the
|
||||
* following signature:
|
||||
* @param output nsIAsyncOutputStream
|
||||
* The stream to copy to.
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or rejected if any
|
||||
* (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk that is
|
||||
* copied. See stream-utils.js.
|
||||
*
|
||||
* - onClosed(reason) - called when the connection is closed. |reason| is
|
||||
* an optional nsresult or object, typically passed when the transport is
|
||||
* closed due to some error in a underlying stream.
|
||||
*
|
||||
* See ./packets.js and the Remote Debugging Protocol specification for more
|
||||
* details on the format of these packets.
|
||||
*/
|
||||
function DebuggerTransport(input, output) {
|
||||
// EventEmitter.decorate(this);
|
||||
|
||||
this._input = input;
|
||||
// this._scriptableInput = new ScriptableInputStream(input);
|
||||
this._output = output;
|
||||
|
||||
// The current incoming (possibly partial) header, which will determine which
|
||||
// type of Packet |_incoming| below will become.
|
||||
this._incomingHeader = "";
|
||||
// The current incoming Packet object
|
||||
// this._incoming = null;
|
||||
this._incoming = "";
|
||||
// A queue of outgoing Packet objects
|
||||
// this._outgoing = [];
|
||||
this._outgoing = "";
|
||||
|
||||
this.hooks = null;
|
||||
this.active = false;
|
||||
|
||||
this._incomingEnabled = true;
|
||||
this._outgoingEnabled = true;
|
||||
|
||||
this.close = this.close.bind(this);
|
||||
}
|
||||
|
||||
DebuggerTransport.prototype = {
|
||||
/**
|
||||
* Transmit an object as a JSON 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(object) {
|
||||
// this.emit("send", object);
|
||||
|
||||
// let packet = new JSONPacket(this);
|
||||
// packet.object = object;
|
||||
// this._outgoing.push(packet);
|
||||
|
||||
let data = JSON.stringify(object);
|
||||
let data_for_len = DevToolsUtils.utf16to8(data);
|
||||
this._outgoing = data_for_len.length + ':' + data;
|
||||
this._flushOutgoing();
|
||||
},
|
||||
|
||||
/**
|
||||
* Transmit streaming data via a bulk packet.
|
||||
*
|
||||
* This method initiates the bulk send process by queuing up the header data.
|
||||
* The caller receives eventual access to a stream for writing.
|
||||
*
|
||||
* N.B.: Do *not* attempt to close the stream handed to you, as it will
|
||||
* continue to be used by this transport afterwards. Most users should
|
||||
* instead use the provided |copyFrom| function instead.
|
||||
*
|
||||
* @param header Object
|
||||
* This is modeled after the format of JSON packets above, but does not
|
||||
* actually contain the data, but is instead just a routing header:
|
||||
* * actor: Name of actor that will receive the packet
|
||||
* * type: Name of actor's method that should be called on receipt
|
||||
* * length: Size of the data to be sent
|
||||
* @return Promise
|
||||
* The promise will be resolved when you are allowed to write to the
|
||||
* stream with an object containing:
|
||||
* * stream: This output stream should only be used directly if
|
||||
* you can ensure that you will write exactly |length|
|
||||
* bytes and will not close the stream when writing is
|
||||
* complete
|
||||
* * done: If you use the stream directly (instead of |copyFrom|
|
||||
* below), you must signal completion by resolving /
|
||||
* rejecting this deferred. If it's rejected, the
|
||||
* transport will be closed. If an Error is supplied as
|
||||
* a rejection value, it will be logged via |dumpn|. If
|
||||
* you do use |copyFrom|, resolving is taken care of for
|
||||
* you when copying completes.
|
||||
* * copyFrom: A helper function for getting your data onto the
|
||||
* stream that meets the stream handling requirements
|
||||
* above, and has the following signature:
|
||||
* @param input nsIAsyncInputStream
|
||||
* The stream to copy from.
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or
|
||||
* rejected if any (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk
|
||||
* that is copied. See stream-utils.js.
|
||||
*/
|
||||
startBulkSend: function(header) {
|
||||
this.emit("startBulkSend", header);
|
||||
|
||||
let packet = new BulkPacket(this);
|
||||
packet.header = header;
|
||||
this._outgoing.push(packet);
|
||||
this._flushOutgoing();
|
||||
return packet.streamReadyForWriting;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the transport.
|
||||
* @param reason nsresult / object (optional)
|
||||
* The status code or error message that corresponds to the reason for
|
||||
* closing the transport (likely because a stream closed or failed).
|
||||
*/
|
||||
close: function(reason) {
|
||||
this.emit("onClosed", reason);
|
||||
|
||||
this.active = false;
|
||||
this._input.close();
|
||||
this._scriptableInput.close();
|
||||
this._output.close();
|
||||
this._destroyIncoming();
|
||||
this._destroyAllOutgoing();
|
||||
if (this.hooks) {
|
||||
this.hooks.onClosed(reason);
|
||||
this.hooks = null;
|
||||
}
|
||||
if (reason) {
|
||||
dumpn("Transport closed: " + DevToolsUtils.safeErrorString(reason));
|
||||
} else {
|
||||
dumpn("Transport closed.");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The currently outgoing packet (at the top of the queue).
|
||||
*/
|
||||
get _currentOutgoing() { return this._outgoing[0]; },
|
||||
|
||||
/**
|
||||
* Flush data to the outgoing stream. Waits until the output stream notifies
|
||||
* us that it is ready to be written to (via onOutputStreamReady).
|
||||
*/
|
||||
_flushOutgoing: function() {
|
||||
if (!this._outgoingEnabled || this._outgoing.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the top of the packet queue has nothing more to send, remove it.
|
||||
if (this._currentOutgoing.done) {
|
||||
this._finishCurrentOutgoing();
|
||||
}
|
||||
|
||||
if (this._outgoing.length > 0) {
|
||||
// var threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
|
||||
// this._output.asyncWait(this, 0, 0, threadManager.currentThread);
|
||||
// log('send: ' + this._outgoing);
|
||||
_bufferWrite(this._outgoing);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause this transport's attempts to write to the output stream. This is
|
||||
* used when we've temporarily handed off our output stream for writing bulk
|
||||
* data.
|
||||
*/
|
||||
pauseOutgoing: function() {
|
||||
this._outgoingEnabled = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume this transport's attempts to write to the output stream.
|
||||
*/
|
||||
resumeOutgoing: function() {
|
||||
this._outgoingEnabled = true;
|
||||
this._flushOutgoing();
|
||||
},
|
||||
|
||||
// nsIOutputStreamCallback
|
||||
/**
|
||||
* This is called when the output stream is ready for more data to be written.
|
||||
* The current outgoing packet will attempt to write some amount of data, but
|
||||
* may not complete.
|
||||
*/
|
||||
onOutputStreamReady: DevToolsUtils.makeInfallible(function(stream) {
|
||||
if (!this._outgoingEnabled || this._outgoing.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._currentOutgoing.write(stream);
|
||||
} catch(e) {
|
||||
if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
|
||||
this.close(e.result);
|
||||
return;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
this._flushOutgoing();
|
||||
}, "DebuggerTransport.prototype.onOutputStreamReady"),
|
||||
|
||||
/**
|
||||
* Remove the current outgoing packet from the queue upon completion.
|
||||
*/
|
||||
_finishCurrentOutgoing: function() {
|
||||
if (this._currentOutgoing) {
|
||||
this._currentOutgoing.destroy();
|
||||
this._outgoing.shift();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the entire outgoing queue.
|
||||
*/
|
||||
_destroyAllOutgoing: function() {
|
||||
for (let packet of this._outgoing) {
|
||||
packet.destroy();
|
||||
}
|
||||
this._outgoing = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the input stream for reading. Once this method has been called,
|
||||
* we watch for packets on the input stream, and pass them to the appropriate
|
||||
* handlers via this.hooks.
|
||||
*/
|
||||
ready: function() {
|
||||
this.active = true;
|
||||
this._waitForIncoming();
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks the input stream to notify us (via onInputStreamReady) when it is
|
||||
* ready for reading.
|
||||
*/
|
||||
_waitForIncoming: function() {
|
||||
// if (this._incomingEnabled) {
|
||||
// let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
|
||||
// this._input.asyncWait(this, 0, 0, threadManager.currentThread);
|
||||
// }
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause this transport's attempts to read from the input stream. This is
|
||||
* used when we've temporarily handed off our input stream for reading bulk
|
||||
* data.
|
||||
*/
|
||||
pauseIncoming: function() {
|
||||
this._incomingEnabled = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume this transport's attempts to read from the input stream.
|
||||
*/
|
||||
resumeIncoming: function() {
|
||||
this._incomingEnabled = true;
|
||||
this._flushIncoming();
|
||||
this._waitForIncoming();
|
||||
},
|
||||
|
||||
// nsIInputStreamCallback
|
||||
/**
|
||||
* Called when the stream is either readable or closed.
|
||||
*/
|
||||
onInputStreamReady:
|
||||
DevToolsUtils.makeInfallible(function(stream) {
|
||||
try {
|
||||
while(stream.available() && this._incomingEnabled &&
|
||||
this._processIncoming(stream, stream.available())) {}
|
||||
this._waitForIncoming();
|
||||
} catch(e) {
|
||||
if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
|
||||
this.close(e.result);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}, "DebuggerTransport.prototype.onInputStreamReady"),
|
||||
|
||||
/**
|
||||
* Process the incoming data. Will create a new currently incoming Packet if
|
||||
* needed. Tells the incoming Packet to read as much data as it can, but
|
||||
* reading may not complete. The Packet signals that its data is ready for
|
||||
* delivery by calling one of this transport's _on*Ready methods (see
|
||||
* ./packets.js and the _on*Ready methods below).
|
||||
* @return boolean
|
||||
* Whether incoming stream processing should continue for any
|
||||
* remaining data.
|
||||
*/
|
||||
_processIncoming: function(stream, count) {
|
||||
dumpv("Data available: " + count);
|
||||
|
||||
if (!count) {
|
||||
dumpv("Nothing to read, skipping");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this._incoming) {
|
||||
dumpv("Creating a new packet from incoming");
|
||||
|
||||
if (!this._readHeader(stream)) {
|
||||
return false; // Not enough data to read packet type
|
||||
}
|
||||
|
||||
// Attempt to create a new Packet by trying to parse each possible
|
||||
// header pattern.
|
||||
this._incoming = Packet.fromHeader(this._incomingHeader, this);
|
||||
if (!this._incoming) {
|
||||
throw new Error("No packet types for header: " +
|
||||
this._incomingHeader);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._incoming.done) {
|
||||
// We have an incomplete packet, keep reading it.
|
||||
dumpv("Existing packet incomplete, keep reading");
|
||||
this._incoming.read(stream, this._scriptableInput);
|
||||
}
|
||||
} catch(e) {
|
||||
let msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
|
||||
dumpn(msg);
|
||||
|
||||
// Now in an invalid state, shut down the transport.
|
||||
this.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._incoming.done) {
|
||||
// Still not complete, we'll wait for more data.
|
||||
dumpv("Packet not done, wait for more");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ready for next packet
|
||||
this._flushIncoming();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Read as far as we can into the incoming data, attempting to build up a
|
||||
* complete packet header (which terminates with ":"). We'll only read up to
|
||||
* PACKET_HEADER_MAX characters.
|
||||
* @return boolean
|
||||
* True if we now have a complete header.
|
||||
*/
|
||||
_readHeader: function() {
|
||||
let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
|
||||
this._incomingHeader +=
|
||||
StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
|
||||
if (dumpv.wantVerbose) {
|
||||
dumpv("Header read: " + this._incomingHeader);
|
||||
}
|
||||
|
||||
if (this._incomingHeader.endsWith(":")) {
|
||||
if (dumpv.wantVerbose) {
|
||||
dumpv("Found packet header successfully: " + this._incomingHeader);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
|
||||
throw new Error("Failed to parse packet header!");
|
||||
}
|
||||
|
||||
// Not enough data yet.
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the incoming packet is done, log it as needed and clear the buffer.
|
||||
*/
|
||||
_flushIncoming: function() {
|
||||
if (!this._incoming.done) {
|
||||
return;
|
||||
}
|
||||
if (dumpn.wantLogging) {
|
||||
dumpn("Got: " + this._incoming);
|
||||
}
|
||||
this._destroyIncoming();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler triggered by an incoming JSONPacket completing it's |read| method.
|
||||
* Delivers the packet to this.hooks.onPacket.
|
||||
*/
|
||||
_onJSONObjectReady: function(object) {
|
||||
DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
|
||||
// Ensure the transport is still alive by the time this runs.
|
||||
if (this.active) {
|
||||
this.emit("onPacket", object);
|
||||
this.hooks.onPacket(object);
|
||||
}
|
||||
}, "DebuggerTransport instance's this.hooks.onPacket"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler triggered by an incoming BulkPacket entering the |read| phase for
|
||||
* the stream portion of the packet. Delivers info about the incoming
|
||||
* streaming data to this.hooks.onBulkPacket. See the main comment on the
|
||||
* transport at the top of this file for more details.
|
||||
*/
|
||||
_onBulkReadReady: function(...args) {
|
||||
DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
|
||||
// Ensure the transport is still alive by the time this runs.
|
||||
if (this.active) {
|
||||
this.emit("onBulkPacket", ...args);
|
||||
this.hooks.onBulkPacket(...args);
|
||||
}
|
||||
}, "DebuggerTransport instance's this.hooks.onBulkPacket"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all handlers and references related to the current incoming packet,
|
||||
* either because it is now complete or because the transport is closing.
|
||||
*/
|
||||
_destroyIncoming: function() {
|
||||
if (this._incoming) {
|
||||
this._incoming.destroy();
|
||||
}
|
||||
this._incomingHeader = "";
|
||||
this._incoming = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// exports.DebuggerTransport = DebuggerTransport;
|
||||
|
||||
/**
|
||||
* 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 other LocalDebuggerTransport
|
||||
* The other endpoint for this debugger connection.
|
||||
*
|
||||
* @see DebuggerTransport
|
||||
*/
|
||||
function LocalDebuggerTransport(other) {
|
||||
// EventEmitter.decorate(this);
|
||||
|
||||
this.other = other;
|
||||
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 };
|
||||
this.close = this.close.bind(this);
|
||||
}
|
||||
|
||||
LocalDebuggerTransport.prototype = {
|
||||
/**
|
||||
* Transmit a message by directly calling the onPacket handler of the other
|
||||
* endpoint.
|
||||
*/
|
||||
send: function(packet) {
|
||||
// this.emit("send", packet);
|
||||
|
||||
let serial = this._serial.count++;
|
||||
// if (dumpn.wantLogging) {
|
||||
// /* Check 'from' first, as 'echo' packets have both. */
|
||||
// if (packet.from) {
|
||||
// dumpn("Packet " + serial + " sent from " + uneval(packet.from));
|
||||
// } else if (packet.to) {
|
||||
// dumpn("Packet " + serial + " sent to " + uneval(packet.to));
|
||||
// }
|
||||
// }
|
||||
this._deepFreeze(packet);
|
||||
let other = this.other;
|
||||
if (other && other.hooks) {
|
||||
log("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
|
||||
other.hooks.onPacket(packet);
|
||||
}
|
||||
// if (other) {
|
||||
// DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
|
||||
// // Avoid the cost of JSON.stringify() when logging is disabled.
|
||||
// if (dumpn.wantLogging) {
|
||||
// dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
|
||||
// }
|
||||
// if (other.hooks) {
|
||||
// other.emit("onPacket", packet);
|
||||
// other.hooks.onPacket(packet);
|
||||
// }
|
||||
// }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
|
||||
// }
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a streaming bulk packet directly to the onBulkPacket handler of the
|
||||
* other endpoint.
|
||||
*
|
||||
* This case is much simpler than the full DebuggerTransport, since there is
|
||||
* no primary stream we have to worry about managing while we hand it off to
|
||||
* others temporarily. Instead, we can just make a single use pipe and be
|
||||
* done with it.
|
||||
*/
|
||||
startBulkSend: function({actor, type, length}) {
|
||||
this.emit("startBulkSend", {actor, type, length});
|
||||
|
||||
let serial = this._serial.count++;
|
||||
|
||||
dumpn("Sent bulk packet " + serial + " for actor " + actor);
|
||||
if (!this.other) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pipe = new Pipe(true, true, 0, 0, null);
|
||||
|
||||
DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
|
||||
dumpn("Received bulk packet " + serial);
|
||||
if (!this.other.hooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Receiver
|
||||
let deferred = promise.defer();
|
||||
let packet = {
|
||||
actor: actor,
|
||||
type: type,
|
||||
length: length,
|
||||
copyTo: (output) => {
|
||||
let copying =
|
||||
StreamUtils.copyStream(pipe.inputStream, output, length);
|
||||
deferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: pipe.inputStream,
|
||||
done: deferred
|
||||
};
|
||||
|
||||
this.other.emit("onBulkPacket", packet);
|
||||
this.other.hooks.onBulkPacket(packet);
|
||||
|
||||
// Await the result of reading from the stream
|
||||
deferred.promise.then(() => pipe.inputStream.close(), this.close);
|
||||
}, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
|
||||
|
||||
// Sender
|
||||
let sendDeferred = promise.defer();
|
||||
|
||||
// The remote transport is not capable of resolving immediately here, so we
|
||||
// shouldn't be able to either.
|
||||
DevToolsUtils.executeSoon(() => {
|
||||
let copyDeferred = promise.defer();
|
||||
|
||||
sendDeferred.resolve({
|
||||
copyFrom: (input) => {
|
||||
let copying =
|
||||
StreamUtils.copyStream(input, pipe.outputStream, length);
|
||||
copyDeferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: pipe.outputStream,
|
||||
done: copyDeferred
|
||||
});
|
||||
|
||||
// Await the result of writing to the stream
|
||||
copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
|
||||
});
|
||||
|
||||
return sendDeferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the transport.
|
||||
*/
|
||||
close: function() {
|
||||
this.emit("close");
|
||||
|
||||
if (this.other) {
|
||||
// Remove the reference to the other endpoint before calling close(), to
|
||||
// avoid infinite recursion.
|
||||
let other = this.other;
|
||||
this.other = null;
|
||||
other.close();
|
||||
}
|
||||
if (this.hooks) {
|
||||
try {
|
||||
this.hooks.onClosed();
|
||||
} catch(ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
this.hooks = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* An empty method for emulating the DebuggerTransport API.
|
||||
*/
|
||||
ready: function() {},
|
||||
|
||||
/**
|
||||
* Helper function that makes an object fully immutable.
|
||||
*/
|
||||
_deepFreeze: function(object) {
|
||||
Object.freeze(object);
|
||||
for (let prop in object) {
|
||||
// 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 (object.hasOwnProperty(prop) && typeof object === "object" &&
|
||||
!Object.isFrozen(object)) {
|
||||
this._deepFreeze(o[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// exports.LocalDebuggerTransport = LocalDebuggerTransport;
|
||||
|
||||
/**
|
||||
* A transport for the debugging protocol that uses nsIMessageSenders to
|
||||
* exchange packets with servers running in child processes.
|
||||
*
|
||||
* In the parent process, |sender| should be the nsIMessageSender for the
|
||||
* child process. In a child process, |sender| should be the child process
|
||||
* message manager, which sends packets to the parent.
|
||||
*
|
||||
* |prefix| is a string included in the message names, to distinguish
|
||||
* multiple servers running in the same child process.
|
||||
*
|
||||
* This transport exchanges messages named 'debug:<prefix>:packet', where
|
||||
* <prefix> is |prefix|, whose data is the protocol packet.
|
||||
*/
|
||||
// function ChildDebuggerTransport(sender, prefix) {
|
||||
// EventEmitter.decorate(this);
|
||||
|
||||
// this._sender = sender.QueryInterface(Ci.nsIMessageSender);
|
||||
// this._messageName = "debug:" + prefix + ":packet";
|
||||
// }
|
||||
|
||||
// /*
|
||||
// * To avoid confusion, we use 'message' to mean something that
|
||||
// * nsIMessageSender conveys, and 'packet' to mean a remote debugging
|
||||
// * protocol packet.
|
||||
// */
|
||||
// ChildDebuggerTransport.prototype = {
|
||||
// constructor: ChildDebuggerTransport,
|
||||
|
||||
// hooks: null,
|
||||
|
||||
// ready: function () {
|
||||
// this._sender.addMessageListener(this._messageName, this);
|
||||
// },
|
||||
|
||||
// close: function () {
|
||||
// this._sender.removeMessageListener(this._messageName, this);
|
||||
// this.emit("onClosed");
|
||||
// this.hooks.onClosed();
|
||||
// },
|
||||
|
||||
// receiveMessage: function ({data}) {
|
||||
// this.emit("onPacket", data);
|
||||
// this.hooks.onPacket(data);
|
||||
// },
|
||||
|
||||
// send: function (packet) {
|
||||
// this.emit("send", packet);
|
||||
// this._sender.sendAsyncMessage(this._messageName, packet);
|
||||
// },
|
||||
|
||||
// startBulkSend: function() {
|
||||
// throw new Error("Can't send bulk data to child processes.");
|
||||
// }
|
||||
// };
|
||||
|
||||
// exports.ChildDebuggerTransport = ChildDebuggerTransport;
|
||||
|
||||
// // WorkerDebuggerTransport is defined differently depending on whether we are
|
||||
// // on the main thread or a worker thread. In the former case, we are required
|
||||
// // by the devtools loader, and isWorker will be false. Otherwise, we are
|
||||
// // required by the worker loader, and isWorker will be true.
|
||||
// //
|
||||
// // Each worker debugger supports only a single connection to the main thread.
|
||||
// // However, its theoretically possible for multiple servers to connect to the
|
||||
// // same worker. Consequently, each transport has a connection id, to allow
|
||||
// // messages from multiple connections to be multiplexed on a single channel.
|
||||
|
||||
// if (!this.isWorker) {
|
||||
// (function () { // Main thread
|
||||
// /**
|
||||
// * A transport that uses a WorkerDebugger to send packets from the main
|
||||
// * thread to a worker thread.
|
||||
// */
|
||||
// function WorkerDebuggerTransport(dbg, id) {
|
||||
// this._dbg = dbg;
|
||||
// this._id = id;
|
||||
// this.onMessage = this._onMessage.bind(this);
|
||||
// }
|
||||
|
||||
// WorkerDebuggerTransport.prototype = {
|
||||
// constructor: WorkerDebuggerTransport,
|
||||
|
||||
// ready: function () {
|
||||
// this._dbg.addListener(this);
|
||||
// },
|
||||
|
||||
// close: function () {
|
||||
// this._dbg.removeListener(this);
|
||||
// if (this.hooks) {
|
||||
// this.hooks.onClosed();
|
||||
// }
|
||||
// },
|
||||
|
||||
// send: function (packet) {
|
||||
// this._dbg.postMessage(JSON.stringify({
|
||||
// type: "message",
|
||||
// id: this._id,
|
||||
// message: packet
|
||||
// }));
|
||||
// },
|
||||
|
||||
// startBulkSend: function () {
|
||||
// throw new Error("Can't send bulk data from worker threads!");
|
||||
// },
|
||||
|
||||
// _onMessage: function (message) {
|
||||
// let packet = JSON.parse(message);
|
||||
// if (packet.type !== "message" || packet.id !== this._id) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (this.hooks) {
|
||||
// this.hooks.onPacket(packet.message);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
|
||||
// }).call(this);
|
||||
// } else {
|
||||
// (function () { // Worker thread
|
||||
// /*
|
||||
// * A transport that uses a WorkerDebuggerGlobalScope to send packets from a
|
||||
// * worker thread to the main thread.
|
||||
// */
|
||||
// function WorkerDebuggerTransport(scope, id) {
|
||||
// this._scope = scope;
|
||||
// this._id = id;
|
||||
// this._onMessage = this._onMessage.bind(this);
|
||||
// }
|
||||
|
||||
// WorkerDebuggerTransport.prototype = {
|
||||
// constructor: WorkerDebuggerTransport,
|
||||
|
||||
// ready: function () {
|
||||
// this._scope.addEventListener("message", this._onMessage);
|
||||
// },
|
||||
|
||||
// close: function () {
|
||||
// this._scope.removeEventListener("message", this._onMessage);
|
||||
// if (this.hooks) {
|
||||
// this.hooks.onClosed();
|
||||
// }
|
||||
// },
|
||||
|
||||
// send: function (packet) {
|
||||
// this._scope.postMessage(JSON.stringify({
|
||||
// type: "message",
|
||||
// id: this._id,
|
||||
// message: packet
|
||||
// }));
|
||||
// },
|
||||
|
||||
// startBulkSend: function () {
|
||||
// throw new Error("Can't send bulk data from worker threads!");
|
||||
// },
|
||||
|
||||
// _onMessage: function (event) {
|
||||
// let packet = JSON.parse(event.data);
|
||||
// if (packet.type !== "message" || packet.id !== this._id) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (this.hooks) {
|
||||
// this.hooks.onPacket(packet.message);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
|
||||
// }).call(this);
|
||||
// }
|
||||
|
||||
// });
|
||||
+2068
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user