зеркало из https://github.com/mozilla/pjs.git
Bug 673148 - (async-webconsole) Part 3 - Make JS evaluation and object inspection async; r=rcampbell
This commit is contained in:
Родитель
c6387f8010
Коммит
b0bcc42199
|
@ -487,11 +487,11 @@ var Scratchpad = {
|
|||
GetStringFromName("propertyPanel.updateButton.label"),
|
||||
accesskey: this.strings.
|
||||
GetStringFromName("propertyPanel.updateButton.accesskey"),
|
||||
oncommand: function () {
|
||||
oncommand: function _SP_PP_Update_onCommand() {
|
||||
let [error, result] = self.evalForContext(aEvalString);
|
||||
|
||||
if (!error) {
|
||||
propPanel.treeView.data = result;
|
||||
propPanel.treeView.data = { object: result };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -499,8 +499,9 @@ var Scratchpad = {
|
|||
|
||||
let doc = this.browserWindow.document;
|
||||
let parent = doc.getElementById("mainPopupSet");
|
||||
let title = aOutputObject.toString();
|
||||
propPanel = new PropertyPanel(parent, doc, title, aOutputObject, buttons);
|
||||
let title = String(aOutputObject);
|
||||
propPanel = new PropertyPanel(parent, title, { object: aOutputObject },
|
||||
buttons);
|
||||
|
||||
let panel = propPanel.panel;
|
||||
panel.setAttribute("class", "scratchpad_propertyPanel");
|
||||
|
|
|
@ -23,6 +23,7 @@ let Services = tempScope.Services;
|
|||
let gConsoleStorage = tempScope.ConsoleAPIStorage;
|
||||
let WebConsoleUtils = tempScope.WebConsoleUtils;
|
||||
let l10n = WebConsoleUtils.l10n;
|
||||
let JSPropertyProvider = tempScope.JSPropertyProvider;
|
||||
tempScope = null;
|
||||
|
||||
let _alive = true; // Track if this content script should still be alive.
|
||||
|
@ -32,7 +33,6 @@ let _alive = true; // Track if this content script should still be alive.
|
|||
*/
|
||||
let Manager = {
|
||||
get window() content,
|
||||
get console() this.window.console,
|
||||
sandbox: null,
|
||||
hudId: null,
|
||||
_sequence: 0,
|
||||
|
@ -60,11 +60,7 @@ let Manager = {
|
|||
|
||||
// Need to track the owner XUL window to listen to the unload and TabClose
|
||||
// events, to avoid memory leaks.
|
||||
let xulWindow = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler.ownerDocument.defaultView;
|
||||
|
||||
let xulWindow = this._xulWindow();
|
||||
xulWindow.addEventListener("unload", this._onXULWindowClose, false);
|
||||
|
||||
let tabContainer = xulWindow.gBrowser.tabContainer;
|
||||
|
@ -357,6 +353,19 @@ let Manager = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the XUL window that owns the content script.
|
||||
* @private
|
||||
* @return Window
|
||||
* The XUL window that owns the content script.
|
||||
*/
|
||||
_xulWindow: function Manager__xulWindow()
|
||||
{
|
||||
return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler.ownerDocument.defaultView;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the Web Console content script instance.
|
||||
*/
|
||||
|
@ -366,11 +375,7 @@ let Manager = {
|
|||
Services.obs.removeObserver(this, "quit-application-granted");
|
||||
|
||||
_alive = false;
|
||||
let xulWindow = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler.ownerDocument.defaultView;
|
||||
|
||||
let xulWindow = this._xulWindow();
|
||||
xulWindow.removeEventListener("unload", this._onXULWindowClose, false);
|
||||
let tabContainer = xulWindow.gBrowser.tabContainer;
|
||||
tabContainer.removeEventListener("TabClose", this._onTabClose, false);
|
||||
|
@ -389,11 +394,238 @@ let Manager = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* JSTerm helper functions.
|
||||
*
|
||||
* Defines a set of functions ("helper functions") that are available from the
|
||||
* Web Console but not from the web page.
|
||||
*
|
||||
* A list of helper functions used by Firebug can be found here:
|
||||
* http://getfirebug.com/wiki/index.php/Command_Line_API
|
||||
*/
|
||||
function JSTermHelper(aJSTerm)
|
||||
{
|
||||
/**
|
||||
* Find a node by ID.
|
||||
*
|
||||
* @param string aId
|
||||
* The ID of the element you want.
|
||||
* @return nsIDOMNode or null
|
||||
* The result of calling document.getElementById(aId).
|
||||
*/
|
||||
aJSTerm.sandbox.$ = function JSTH_$(aId)
|
||||
{
|
||||
return aJSTerm.window.document.getElementById(aId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the nodes matching a CSS selector.
|
||||
*
|
||||
* @param string aSelector
|
||||
* A string that is passed to window.document.querySelectorAll.
|
||||
* @return nsIDOMNodeList
|
||||
* Returns the result of document.querySelectorAll(aSelector).
|
||||
*/
|
||||
aJSTerm.sandbox.$$ = function JSTH_$$(aSelector)
|
||||
{
|
||||
return aJSTerm.window.document.querySelectorAll(aSelector);
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs an xPath query and returns all matched nodes.
|
||||
*
|
||||
* @param string aXPath
|
||||
* xPath search query to execute.
|
||||
* @param [optional] nsIDOMNode aContext
|
||||
* Context to run the xPath query on. Uses window.document if not set.
|
||||
* @returns array of nsIDOMNode
|
||||
*/
|
||||
aJSTerm.sandbox.$x = function JSTH_$x(aXPath, aContext)
|
||||
{
|
||||
let nodes = [];
|
||||
let doc = aJSTerm.window.document;
|
||||
let aContext = aContext || doc;
|
||||
|
||||
try {
|
||||
let results = doc.evaluate(aXPath, aContext, null,
|
||||
Ci.nsIDOMXPathResult.ANY_TYPE, null);
|
||||
let node;
|
||||
while (node = results.iterateNext()) {
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
aJSTerm.console.error(ex.message);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the currently selected object in the highlighter.
|
||||
*
|
||||
* Warning: this implementation crosses the process boundaries! This is not
|
||||
* usable within a remote browser. To implement this feature correctly we need
|
||||
* support for remote inspection capabilities within the Inspector as well.
|
||||
*
|
||||
* @return nsIDOMElement|null
|
||||
* The DOM element currently selected in the highlighter.
|
||||
*/
|
||||
Object.defineProperty(aJSTerm.sandbox, "$0", {
|
||||
get: function() {
|
||||
try {
|
||||
return Manager._xulWindow().InspectorUI.selection;
|
||||
}
|
||||
catch (ex) {
|
||||
aJSTerm.console.error(ex.message);
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Clears the output of the JSTerm.
|
||||
*/
|
||||
aJSTerm.sandbox.clear = function JSTH_clear()
|
||||
{
|
||||
aJSTerm.helperEvaluated = true;
|
||||
Manager.sendMessage("JSTerm:ClearOutput", {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the result of Object.keys(aObject).
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to return the property names from.
|
||||
* @returns array of string
|
||||
*/
|
||||
aJSTerm.sandbox.keys = function JSTH_keys(aObject)
|
||||
{
|
||||
return Object.keys(WebConsoleUtils.unwrap(aObject));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the values of all properties on aObject.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to display the values from.
|
||||
* @returns array of string
|
||||
*/
|
||||
aJSTerm.sandbox.values = function JSTH_values(aObject)
|
||||
{
|
||||
let arrValues = [];
|
||||
let obj = WebConsoleUtils.unwrap(aObject);
|
||||
|
||||
try {
|
||||
for (let prop in obj) {
|
||||
arrValues.push(obj[prop]);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
aJSTerm.console.error(ex.message);
|
||||
}
|
||||
return arrValues;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a help window in MDN.
|
||||
*/
|
||||
aJSTerm.sandbox.help = function JSTH_help()
|
||||
{
|
||||
aJSTerm.helperEvaluated = true;
|
||||
aJSTerm.window.open(
|
||||
"https://developer.mozilla.org/AppLinks/WebConsoleHelp?locale=" +
|
||||
aJSTerm.window.navigator.language, "help", "");
|
||||
};
|
||||
|
||||
/**
|
||||
* Inspects the passed aObject. This is done by opening the PropertyPanel.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to inspect.
|
||||
*/
|
||||
aJSTerm.sandbox.inspect = function JSTH_inspect(aObject)
|
||||
{
|
||||
if (!WebConsoleUtils.isObjectInspectable(aObject)) {
|
||||
return aObject;
|
||||
}
|
||||
|
||||
aJSTerm.helperEvaluated = true;
|
||||
|
||||
let message = {
|
||||
input: aJSTerm._evalInput,
|
||||
objectCacheId: Manager.sequenceId,
|
||||
};
|
||||
|
||||
message.resultObject =
|
||||
aJSTerm.prepareObjectForRemote(WebConsoleUtils.unwrap(aObject),
|
||||
message.objectCacheId);
|
||||
|
||||
Manager.sendMessage("JSTerm:InspectObject", message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prints aObject to the output.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to print to the output.
|
||||
* @return string
|
||||
*/
|
||||
aJSTerm.sandbox.pprint = function JSTH_pprint(aObject)
|
||||
{
|
||||
aJSTerm.helperEvaluated = true;
|
||||
if (aObject === null || aObject === undefined || aObject === true ||
|
||||
aObject === false) {
|
||||
aJSTerm.console.error(l10n.getStr("helperFuncUnsupportedTypeError"));
|
||||
return;
|
||||
}
|
||||
else if (typeof aObject == "function") {
|
||||
aJSTerm.helperRawOutput = true;
|
||||
return aObject + "\n";
|
||||
}
|
||||
|
||||
aJSTerm.helperRawOutput = true;
|
||||
|
||||
let output = [];
|
||||
let pairs = WebConsoleUtils.namesAndValuesOf(WebConsoleUtils.unwrap(aObject));
|
||||
pairs.forEach(function(aPair) {
|
||||
output.push(aPair.name + ": " + aPair.value);
|
||||
});
|
||||
|
||||
return " " + output.join("\n ");
|
||||
};
|
||||
|
||||
/**
|
||||
* Print a string to the output, as-is.
|
||||
*
|
||||
* @param string aString
|
||||
* A string you want to output.
|
||||
* @returns void
|
||||
*/
|
||||
aJSTerm.sandbox.print = function JSTH_print(aString)
|
||||
{
|
||||
aJSTerm.helperEvaluated = true;
|
||||
aJSTerm.helperRawOutput = true;
|
||||
return String(aString);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The JavaScript terminal is meant to allow remote code execution for the Web
|
||||
* Console.
|
||||
*/
|
||||
let JSTerm = {
|
||||
get window() Manager.window,
|
||||
get console() this.window.console,
|
||||
|
||||
/**
|
||||
* The Cu.Sandbox() object where code is evaluated.
|
||||
*/
|
||||
sandbox: null,
|
||||
|
||||
_messageHandlers: {},
|
||||
|
||||
/**
|
||||
* Evaluation result objects are cached in this object. The chrome process can
|
||||
* request any object based on its ID.
|
||||
|
@ -406,16 +638,112 @@ let JSTerm = {
|
|||
init: function JST_init()
|
||||
{
|
||||
this._objectCache = {};
|
||||
this._messageHandlers = {
|
||||
"JSTerm:EvalRequest": this.handleEvalRequest,
|
||||
"JSTerm:GetEvalObject": this.handleGetEvalObject,
|
||||
"JSTerm:Autocomplete": this.handleAutocomplete,
|
||||
"JSTerm:ClearObjectCache": this.handleClearObjectCache,
|
||||
};
|
||||
|
||||
Manager.addMessageHandler("JSTerm:GetEvalObject",
|
||||
this.handleGetEvalObject.bind(this));
|
||||
Manager.addMessageHandler("JSTerm:ClearObjectCache",
|
||||
this.handleClearObjectCache.bind(this));
|
||||
for (let name in this._messageHandlers) {
|
||||
let handler = this._messageHandlers[name].bind(this);
|
||||
Manager.addMessageHandler(name, handler);
|
||||
}
|
||||
|
||||
this._createSandbox();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "JSTerm:EvalRequest" remote message. This method evaluates
|
||||
* user input in the JavaScript sandbox and sends the result back to the
|
||||
* remote process. The "JSTerm:EvalResult" message includes the following
|
||||
* data:
|
||||
* - id - the same ID as the EvalRequest (for tracking purposes).
|
||||
* - input - the JS string that was evaluated.
|
||||
* - resultString - the evaluation result converted to a string formatted
|
||||
* for display.
|
||||
* - timestamp - timestamp when evaluation occurred (Date.now(),
|
||||
* milliseconds since the UNIX epoch).
|
||||
* - inspectable - boolean that tells if the evaluation result object can be
|
||||
* inspected or not.
|
||||
* - error - the evaluation exception object (if any).
|
||||
* - errorMessage - the exception object converted to a string (if any error
|
||||
* occurred).
|
||||
* - helperResult - boolean that tells if a JSTerm helper was evaluated.
|
||||
* - helperRawOutput - boolean that tells if the helper evaluation result
|
||||
* should be displayed as raw output.
|
||||
*
|
||||
* If the result object is inspectable then two additional properties are
|
||||
* included:
|
||||
* - childrenCacheId - tells where child objects are cached. This is the
|
||||
* same as aRequest.resultCacheId.
|
||||
* - resultObject - the result object prepared for the remote process. See
|
||||
* this.prepareObjectForRemote().
|
||||
*
|
||||
* @param object aRequest
|
||||
* The code evaluation request object:
|
||||
* - id - request ID.
|
||||
* - str - string to evaluate.
|
||||
* - resultCacheId - where to cache the evaluation child objects.
|
||||
*/
|
||||
handleEvalRequest: function JST_handleEvalRequest(aRequest)
|
||||
{
|
||||
let id = aRequest.id;
|
||||
let input = aRequest.str;
|
||||
let result, error = null;
|
||||
let timestamp;
|
||||
|
||||
this.helperEvaluated = false;
|
||||
this.helperRawOutput = false;
|
||||
this._evalInput = input;
|
||||
try {
|
||||
timestamp = Date.now();
|
||||
result = this.evalInSandbox(input);
|
||||
}
|
||||
catch (ex) {
|
||||
error = ex;
|
||||
}
|
||||
delete this._evalInput;
|
||||
|
||||
let inspectable = !error && WebConsoleUtils.isObjectInspectable(result);
|
||||
let resultString = undefined;
|
||||
if (!error) {
|
||||
resultString = this.helperRawOutput ? result :
|
||||
WebConsoleUtils.formatResult(result);
|
||||
}
|
||||
|
||||
let message = {
|
||||
id: id,
|
||||
input: input,
|
||||
resultString: resultString,
|
||||
timestamp: timestamp,
|
||||
error: error,
|
||||
errorMessage: error ? String(error) : null,
|
||||
inspectable: inspectable,
|
||||
helperResult: this.helperEvaluated,
|
||||
helperRawOutput: this.helperRawOutput,
|
||||
};
|
||||
|
||||
if (inspectable) {
|
||||
message.childrenCacheId = aRequest.resultCacheId;
|
||||
message.resultObject =
|
||||
this.prepareObjectForRemote(result, message.childrenCacheId);
|
||||
}
|
||||
|
||||
Manager.sendMessage("JSTerm:EvalResult", message);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the remote "JSTerm:GetEvalObject" message. This allows the
|
||||
* remote Web Console instance to retrieve an object from the content process.
|
||||
* The "JSTerm:EvalObject" message is sent back to the remote process:
|
||||
* - id - the request ID, used to trace back to the initial request.
|
||||
* - cacheId - the cache ID where the requested object is stored.
|
||||
* - objectId - the ID of the object being sent.
|
||||
* - object - the object representation prepared for remote inspection. See
|
||||
* this.prepareObjectForRemote().
|
||||
* - childrenCacheId - the cache ID where any child object of |object| are
|
||||
* stored.
|
||||
*
|
||||
* @param object aRequest
|
||||
* The message that requests the content object. Properties: cacheId,
|
||||
|
@ -481,8 +809,7 @@ let JSTerm = {
|
|||
* method in aObject. Each element describes the property. For details
|
||||
* see WebConsoleUtils.namesAndValuesOf().
|
||||
*/
|
||||
prepareObjectForRemote:
|
||||
function JST_prepareObjectForRemote(aObject, aCacheId)
|
||||
prepareObjectForRemote: function JST_prepareObjectForRemote(aObject, aCacheId)
|
||||
{
|
||||
// Cache the properties that have inspectable values.
|
||||
let propCache = this._objectCache[aCacheId] || {};
|
||||
|
@ -494,14 +821,103 @@ let JSTerm = {
|
|||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "JSTerm:Autocomplete" remote message. This handler provides
|
||||
* completion results for user input. The "JSterm:AutocompleteProperties"
|
||||
* message is sent to the remote process:
|
||||
* - id - the same as request ID.
|
||||
* - input - the user input (same as in the request message).
|
||||
* - matches - an array of matched properties (strings).
|
||||
* - matchProp - the part that was used from the user input for finding the
|
||||
* matches. For details see the JSPropertyProvider description and
|
||||
* implementation.
|
||||
*
|
||||
*
|
||||
* @param object aRequest
|
||||
* The remote request object which holds two properties: an |id| and
|
||||
* the user |input|.
|
||||
*/
|
||||
handleAutocomplete: function JST_handleAutocomplete(aRequest)
|
||||
{
|
||||
let result = JSPropertyProvider(this.window, aRequest.input) || {};
|
||||
let message = {
|
||||
id: aRequest.id,
|
||||
input: aRequest.input,
|
||||
matches: result.matches || [],
|
||||
matchProp: result.matchProp,
|
||||
};
|
||||
Manager.sendMessage("JSTerm:AutocompleteProperties", message);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the JavaScript sandbox where user input is evaluated.
|
||||
* @private
|
||||
*/
|
||||
_createSandbox: function JST__createSandbox()
|
||||
{
|
||||
this.sandbox = new Cu.Sandbox(this.window, {
|
||||
sandboxPrototype: this.window,
|
||||
wantXrays: false,
|
||||
});
|
||||
|
||||
this.sandbox.console = this.console;
|
||||
|
||||
JSTermHelper(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Evaluates a string in the sandbox.
|
||||
*
|
||||
* @param string aString
|
||||
* String to evaluate in the sandbox.
|
||||
* @returns something
|
||||
* The result of the evaluation.
|
||||
*/
|
||||
evalInSandbox: function JST_evalInSandbox(aString)
|
||||
{
|
||||
// The help function needs to be easy to guess, so we make the () optional
|
||||
if (aString.trim() == "help" || aString.trim() == "?") {
|
||||
aString = "help()";
|
||||
}
|
||||
|
||||
let window = WebConsoleUtils.unwrap(this.sandbox.window);
|
||||
let $ = null, $$ = null;
|
||||
|
||||
// We prefer to execute the page-provided implementations for the $() and
|
||||
// $$() functions.
|
||||
if (typeof window.$ == "function") {
|
||||
$ = this.sandbox.$;
|
||||
delete this.sandbox.$;
|
||||
}
|
||||
if (typeof window.$$ == "function") {
|
||||
$$ = this.sandbox.$$;
|
||||
delete this.sandbox.$$;
|
||||
}
|
||||
|
||||
let result = Cu.evalInSandbox(aString, this.sandbox, "1.8",
|
||||
"Web Console", 1);
|
||||
|
||||
if ($) {
|
||||
this.sandbox.$ = $;
|
||||
}
|
||||
if ($$) {
|
||||
this.sandbox.$$ = $$;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the JSTerm instance.
|
||||
*/
|
||||
destroy: function JST_destroy()
|
||||
{
|
||||
Manager.removeMessageHandler("JSTerm:GetEvalObject");
|
||||
Manager.removeMessageHandler("JSTerm:ClearObjectCache");
|
||||
for (let name in this._messageHandlers) {
|
||||
Manager.removeMessageHandler(name);
|
||||
}
|
||||
|
||||
delete this.sandbox;
|
||||
delete this._messageHandlers;
|
||||
delete this._objectCache;
|
||||
},
|
||||
};
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -12,7 +12,6 @@ include $(DEPTH)/config/autoconf.mk
|
|||
|
||||
EXTRA_JS_MODULES = \
|
||||
PropertyPanel.jsm \
|
||||
PropertyPanelAsync.jsm \
|
||||
NetworkHelper.jsm \
|
||||
AutocompletePopup.jsm \
|
||||
WebConsoleUtils.jsm \
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* 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 = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
@ -11,242 +13,18 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView",
|
||||
"namesAndValuesOf", "isNonNativeGetter"];
|
||||
XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", function () {
|
||||
let obj = {};
|
||||
Cu.import("resource:///modules/WebConsoleUtils.jsm", obj);
|
||||
return obj.WebConsoleUtils;
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// Helper for PropertyTreeView
|
||||
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
|
||||
|
||||
const TYPE_OBJECT = 0, TYPE_FUNCTION = 1, TYPE_ARRAY = 2, TYPE_OTHER = 3;
|
||||
|
||||
/**
|
||||
* Figures out the type of aObject and the string to display in the tree.
|
||||
*
|
||||
* @param object aObject
|
||||
* The object to operate on.
|
||||
* @returns object
|
||||
* A object with the form:
|
||||
* {
|
||||
* type: TYPE_OBJECT || TYPE_FUNCTION || TYPE_ARRAY || TYPE_OTHER,
|
||||
* display: string for displaying the object in the tree
|
||||
* }
|
||||
*/
|
||||
function presentableValueFor(aObject)
|
||||
{
|
||||
if (aObject === null || aObject === undefined) {
|
||||
return {
|
||||
type: TYPE_OTHER,
|
||||
display: aObject === undefined ? "undefined" : "null"
|
||||
};
|
||||
}
|
||||
|
||||
let presentable;
|
||||
switch (aObject.constructor && aObject.constructor.name) {
|
||||
case "Array":
|
||||
return {
|
||||
type: TYPE_ARRAY,
|
||||
display: "Array"
|
||||
};
|
||||
|
||||
case "String":
|
||||
return {
|
||||
type: TYPE_OTHER,
|
||||
display: "\"" + aObject + "\""
|
||||
};
|
||||
|
||||
case "Date":
|
||||
case "RegExp":
|
||||
case "Number":
|
||||
case "Boolean":
|
||||
return {
|
||||
type: TYPE_OTHER,
|
||||
display: aObject
|
||||
};
|
||||
|
||||
case "Iterator":
|
||||
return {
|
||||
type: TYPE_OTHER,
|
||||
display: "Iterator"
|
||||
};
|
||||
|
||||
case "Function":
|
||||
presentable = aObject.toString();
|
||||
return {
|
||||
type: TYPE_FUNCTION,
|
||||
display: presentable.substring(0, presentable.indexOf(')') + 1)
|
||||
};
|
||||
|
||||
default:
|
||||
presentable = aObject.toString();
|
||||
let m = /^\[object (\S+)\]/.exec(presentable);
|
||||
|
||||
try {
|
||||
if (typeof aObject == "object" && typeof aObject.next == "function" &&
|
||||
m && m[1] == "Generator") {
|
||||
return {
|
||||
type: TYPE_OTHER,
|
||||
display: m[1]
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
// window.history.next throws in the typeof check above.
|
||||
return {
|
||||
type: TYPE_OBJECT,
|
||||
display: m ? m[1] : "Object"
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof aObject == "object" && typeof aObject.__iterator__ == "function") {
|
||||
return {
|
||||
type: TYPE_OTHER,
|
||||
display: "Iterator"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: TYPE_OBJECT,
|
||||
display: m ? m[1] : "Object"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given function is native or not.
|
||||
*
|
||||
* @param function aFunction
|
||||
* The function you want to check if it is native or not.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the given function is native, false otherwise.
|
||||
*/
|
||||
function isNativeFunction(aFunction)
|
||||
{
|
||||
return typeof aFunction == "function" && !("prototype" in aFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given property of the provided object is a non-native getter or
|
||||
* not.
|
||||
*
|
||||
* @param object aObject
|
||||
* The object that contains the property.
|
||||
*
|
||||
* @param string aProp
|
||||
* The property you want to check if it is a getter or not.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the given property is a getter, false otherwise.
|
||||
*/
|
||||
function isNonNativeGetter(aObject, aProp) {
|
||||
if (typeof aObject != "object") {
|
||||
return false;
|
||||
}
|
||||
let desc;
|
||||
while (aObject) {
|
||||
try {
|
||||
if (desc = Object.getOwnPropertyDescriptor(aObject, aProp)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
// Native getters throw here. See bug 520882.
|
||||
if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
|
||||
ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
|
||||
return false;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
aObject = Object.getPrototypeOf(aObject);
|
||||
}
|
||||
if (desc && desc.get && !isNativeFunction(desc.get)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of property name value pairs for the tree.
|
||||
*
|
||||
* @param object aObject
|
||||
* The object to get properties for.
|
||||
* @returns array of object
|
||||
* Objects have the name, value, display, type, children properties.
|
||||
*/
|
||||
function namesAndValuesOf(aObject)
|
||||
{
|
||||
let pairs = [];
|
||||
let value, presentable;
|
||||
|
||||
let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
|
||||
|
||||
for (var propName in aObject) {
|
||||
// See bug 632275: skip deprecated width and height properties.
|
||||
if (isDOMDocument && (propName == "width" || propName == "height")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Also skip non-native getters.
|
||||
if (isNonNativeGetter(aObject, propName)) {
|
||||
value = ""; // Value is never displayed.
|
||||
presentable = {type: TYPE_OTHER, display: "Getter"};
|
||||
}
|
||||
else {
|
||||
try {
|
||||
value = aObject[propName];
|
||||
presentable = presentableValueFor(value);
|
||||
}
|
||||
catch (ex) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let pair = {};
|
||||
pair.name = propName;
|
||||
pair.display = propName + ": " + presentable.display;
|
||||
pair.type = presentable.type;
|
||||
pair.value = value;
|
||||
|
||||
// Convert the pair.name to a number for later sorting.
|
||||
pair.nameNumber = parseFloat(pair.name)
|
||||
if (isNaN(pair.nameNumber)) {
|
||||
pair.nameNumber = false;
|
||||
}
|
||||
|
||||
pairs.push(pair);
|
||||
}
|
||||
|
||||
pairs.sort(function(a, b)
|
||||
{
|
||||
// Sort numbers.
|
||||
if (a.nameNumber !== false && b.nameNumber === false) {
|
||||
return -1;
|
||||
}
|
||||
else if (a.nameNumber === false && b.nameNumber !== false) {
|
||||
return 1;
|
||||
}
|
||||
else if (a.nameNumber !== false && b.nameNumber !== false) {
|
||||
return a.nameNumber - b.nameNumber;
|
||||
}
|
||||
// Sort string.
|
||||
else if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
else if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// PropertyTreeView.
|
||||
|
||||
|
||||
/**
|
||||
* This is an implementation of the nsITreeView interface. For comments on the
|
||||
* interface properties, see the documentation:
|
||||
|
@ -254,30 +32,87 @@ function namesAndValuesOf(aObject)
|
|||
*/
|
||||
var PropertyTreeView = function() {
|
||||
this._rows = [];
|
||||
this._objectCache = {};
|
||||
};
|
||||
|
||||
PropertyTreeView.prototype = {
|
||||
|
||||
/**
|
||||
* Stores the visible rows of the tree.
|
||||
* @private
|
||||
*/
|
||||
_rows: null,
|
||||
|
||||
/**
|
||||
* Stores the nsITreeBoxObject for this tree.
|
||||
* @private
|
||||
*/
|
||||
_treeBox: null,
|
||||
|
||||
/**
|
||||
* Stores cached information about local objects being inspected.
|
||||
* @private
|
||||
*/
|
||||
_objectCache: null,
|
||||
|
||||
/**
|
||||
* Use this setter to update the content of the tree.
|
||||
*
|
||||
* @param object aObject
|
||||
* The new object to be displayed in the tree.
|
||||
* @returns void
|
||||
* @param object aData
|
||||
* A meta object that holds information about the object you want to
|
||||
* display in the property panel. Object properties:
|
||||
* - object:
|
||||
* This is the raw object you want to display. You can only provide
|
||||
* this object if you want the property panel to work in sync mode.
|
||||
* - remoteObject:
|
||||
* An array that holds information on the remote object being
|
||||
* inspected. Each element in this array describes each property in the
|
||||
* remote object. See WebConsoleUtils.namesAndValuesOf() for details.
|
||||
* - rootCacheId:
|
||||
* The cache ID where the objects referenced in remoteObject are found.
|
||||
* - panelCacheId:
|
||||
* The cache ID where any object retrieved by this property panel
|
||||
* instance should be stored into.
|
||||
* - remoteObjectProvider:
|
||||
* A function that is invoked when a new object is needed. This is
|
||||
* called when the user tries to expand an inspectable property. The
|
||||
* callback must take four arguments:
|
||||
* - fromCacheId:
|
||||
* Tells from where to retrieve the object the user picked (from
|
||||
* which cache ID).
|
||||
* - objectId:
|
||||
* The object ID the user wants.
|
||||
* - panelCacheId:
|
||||
* Tells in which cache ID to store the objects referenced by
|
||||
* objectId so they can be retrieved later.
|
||||
* - callback:
|
||||
* The callback function to be invoked when the remote object is
|
||||
* received. This function takes one argument: the raw message
|
||||
* received from the Web Console content script.
|
||||
*/
|
||||
set data(aObject) {
|
||||
set data(aData) {
|
||||
let oldLen = this._rows.length;
|
||||
this._rows = this.getChildItems(aObject, true);
|
||||
|
||||
this._cleanup();
|
||||
|
||||
if (!aData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aData.remoteObject) {
|
||||
this._rootCacheId = aData.rootCacheId;
|
||||
this._panelCacheId = aData.panelCacheId;
|
||||
this._remoteObjectProvider = aData.remoteObjectProvider;
|
||||
this._rows = [].concat(aData.remoteObject);
|
||||
this._updateRemoteObject(this._rows, 0);
|
||||
}
|
||||
else if (aData.object) {
|
||||
this._rows = this._inspectObject(aData.object);
|
||||
}
|
||||
else {
|
||||
throw new Error("First argument must have a .remoteObject or " +
|
||||
"an .object property!");
|
||||
}
|
||||
|
||||
if (this._treeBox) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
if (oldLen) {
|
||||
|
@ -289,53 +124,66 @@ PropertyTreeView.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Generates the child items for the treeView of a given aItem. If there is
|
||||
* already a children property on the aItem, this cached one is returned.
|
||||
* Update a remote object so it can be used with the tree view. This method
|
||||
* adds properties to each array element.
|
||||
*
|
||||
* @param object aItem
|
||||
* An item of the tree's elements to generate the children for.
|
||||
* @param boolean aRootElement
|
||||
* If set, aItem is handled as an JS object and not as an item
|
||||
* element of the tree.
|
||||
* @returns array of objects
|
||||
* Child items of aItem.
|
||||
* @private
|
||||
* @param array aObject
|
||||
* The remote object you want prepared for use with the tree view.
|
||||
* @param number aLevel
|
||||
* The level you want to give to each property in the remote object.
|
||||
*/
|
||||
getChildItems: function(aItem, aRootElement)
|
||||
_updateRemoteObject: function PTV__updateRemoteObject(aObject, aLevel)
|
||||
{
|
||||
// If item.children is an array, then the children has already been
|
||||
// computed and can get returned directly.
|
||||
// Skip this checking if aRootElement is true. It could happen, that aItem
|
||||
// is passed as ({children:[1,2,3]}) which would be true, although these
|
||||
// "kind" of children has no value/type etc. data as needed to display in
|
||||
// the tree. As the passed ({children:[1,2,3]}) are instanceof
|
||||
// itsWindow.Array and not this modules's global Array
|
||||
// aItem.children instanceof Array can't be true, but for saftey the
|
||||
// !aRootElement is kept here.
|
||||
if (!aRootElement && aItem && aItem.children instanceof Array) {
|
||||
return aItem.children;
|
||||
}
|
||||
aObject.forEach(function(aElement) {
|
||||
aElement.level = aLevel;
|
||||
aElement.isOpened = false;
|
||||
aElement.children = null;
|
||||
});
|
||||
},
|
||||
|
||||
let pairs;
|
||||
let newPairLevel;
|
||||
/**
|
||||
* Inspect a local object.
|
||||
*
|
||||
* @private
|
||||
* @param object aObject
|
||||
* The object you want to inspect.
|
||||
*/
|
||||
_inspectObject: function PTV__inspectObject(aObject)
|
||||
{
|
||||
this._objectCache = {};
|
||||
this._remoteObjectProvider = this._localObjectProvider.bind(this);
|
||||
let children = WebConsoleUtils.namesAndValuesOf(aObject, this._objectCache);
|
||||
this._updateRemoteObject(children, 0);
|
||||
return children;
|
||||
},
|
||||
|
||||
if (!aRootElement) {
|
||||
newPairLevel = aItem.level + 1;
|
||||
aItem = aItem.value;
|
||||
}
|
||||
else {
|
||||
newPairLevel = 0;
|
||||
}
|
||||
|
||||
pairs = namesAndValuesOf(aItem);
|
||||
|
||||
for each (var pair in pairs) {
|
||||
pair.level = newPairLevel;
|
||||
pair.isOpened = false;
|
||||
pair.children = pair.type == TYPE_OBJECT || pair.type == TYPE_FUNCTION ||
|
||||
pair.type == TYPE_ARRAY;
|
||||
}
|
||||
|
||||
return pairs;
|
||||
/**
|
||||
* An object provider for when the user inspects local objects (not remote
|
||||
* ones).
|
||||
*
|
||||
* @private
|
||||
* @param string aFromCacheId
|
||||
* The cache ID from where to retrieve the desired object.
|
||||
* @param string aObjectId
|
||||
* The ID of the object you want.
|
||||
* @param string aDestCacheId
|
||||
* The ID of the cache where to store any objects referenced by the
|
||||
* desired object.
|
||||
* @param function aCallback
|
||||
* The function you want to receive the object.
|
||||
*/
|
||||
_localObjectProvider:
|
||||
function PTV__localObjectProvider(aFromCacheId, aObjectId, aDestCacheId,
|
||||
aCallback)
|
||||
{
|
||||
let object = WebConsoleUtils.namesAndValuesOf(this._objectCache[aObjectId],
|
||||
this._objectCache);
|
||||
aCallback({cacheId: aFromCacheId,
|
||||
objectId: aObjectId,
|
||||
object: object,
|
||||
childrenCacheId: aDestCacheId || aFromCacheId,
|
||||
});
|
||||
},
|
||||
|
||||
/** nsITreeView interface implementation **/
|
||||
|
@ -344,10 +192,19 @@ PropertyTreeView.prototype = {
|
|||
|
||||
get rowCount() { return this._rows.length; },
|
||||
setTree: function(treeBox) { this._treeBox = treeBox; },
|
||||
getCellText: function(idx, column) { return this._rows[idx].display; },
|
||||
getLevel: function(idx) { return this._rows[idx].level; },
|
||||
isContainer: function(idx) { return !!this._rows[idx].children; },
|
||||
isContainerOpen: function(idx) { return this._rows[idx].isOpened; },
|
||||
getCellText: function(idx, column) {
|
||||
let row = this._rows[idx];
|
||||
return row.name + ": " + row.value;
|
||||
},
|
||||
getLevel: function(idx) {
|
||||
return this._rows[idx].level;
|
||||
},
|
||||
isContainer: function(idx) {
|
||||
return !!this._rows[idx].inspectable;
|
||||
},
|
||||
isContainerOpen: function(idx) {
|
||||
return this._rows[idx].isOpened;
|
||||
},
|
||||
isContainerEmpty: function(idx) { return false; },
|
||||
isSeparator: function(idx) { return false; },
|
||||
isSorted: function() { return false; },
|
||||
|
@ -359,7 +216,7 @@ PropertyTreeView.prototype = {
|
|||
if (this.getLevel(idx) == 0) {
|
||||
return -1;
|
||||
}
|
||||
for (var t = idx - 1; t >= 0 ; t--) {
|
||||
for (var t = idx - 1; t >= 0; t--) {
|
||||
if (this.isContainer(t)) {
|
||||
return t;
|
||||
}
|
||||
|
@ -375,13 +232,13 @@ PropertyTreeView.prototype = {
|
|||
|
||||
toggleOpenState: function(idx)
|
||||
{
|
||||
var item = this._rows[idx];
|
||||
if (!item.children) {
|
||||
let item = this._rows[idx];
|
||||
if (!item.inspectable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._treeBox.beginUpdateBatch();
|
||||
if (item.isOpened) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
item.isOpened = false;
|
||||
|
||||
var thisLevel = item.level;
|
||||
|
@ -394,18 +251,38 @@ PropertyTreeView.prototype = {
|
|||
this._rows.splice(idx + 1, deleteCount);
|
||||
this._treeBox.rowCountChanged(idx + 1, -deleteCount);
|
||||
}
|
||||
}
|
||||
else {
|
||||
item.isOpened = true;
|
||||
|
||||
var toInsert = this.getChildItems(item);
|
||||
item.children = toInsert;
|
||||
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(toInsert));
|
||||
|
||||
this._treeBox.rowCountChanged(idx + 1, toInsert.length);
|
||||
}
|
||||
this._treeBox.invalidateRow(idx);
|
||||
this._treeBox.endUpdateBatch();
|
||||
}
|
||||
else {
|
||||
let levelUpdate = true;
|
||||
let callback = function _onRemoteResponse(aResponse) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
item.isOpened = true;
|
||||
|
||||
if (levelUpdate) {
|
||||
this._updateRemoteObject(aResponse.object, item.level + 1);
|
||||
item.children = aResponse.object;
|
||||
}
|
||||
|
||||
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item.children));
|
||||
|
||||
this._treeBox.rowCountChanged(idx + 1, item.children.length);
|
||||
this._treeBox.invalidateRow(idx);
|
||||
this._treeBox.endUpdateBatch();
|
||||
}.bind(this);
|
||||
|
||||
if (!item.children) {
|
||||
let fromCacheId = item.level > 0 ? this._panelCacheId :
|
||||
this._rootCacheId;
|
||||
this._remoteObjectProvider(fromCacheId, item.objectId,
|
||||
this._panelCacheId, callback);
|
||||
}
|
||||
else {
|
||||
levelUpdate = false;
|
||||
callback({object: item.children});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getImageSrc: function(idx, column) { },
|
||||
|
@ -424,7 +301,21 @@ PropertyTreeView.prototype = {
|
|||
setCellValue: function(row, col, value) { },
|
||||
setCellText: function(row, col, value) { },
|
||||
drop: function(index, orientation, dataTransfer) { },
|
||||
canDrop: function(index, orientation, dataTransfer) { return false; }
|
||||
canDrop: function(index, orientation, dataTransfer) { return false; },
|
||||
|
||||
_cleanup: function PTV__cleanup()
|
||||
{
|
||||
if (this._rows.length) {
|
||||
// Reset the existing _rows children to the initial state.
|
||||
this._updateRemoteObject(this._rows, 0);
|
||||
this._rows = [];
|
||||
}
|
||||
|
||||
delete this._objectCache;
|
||||
delete this._rootCacheId;
|
||||
delete this._panelCacheId;
|
||||
delete this._remoteObjectProvider;
|
||||
},
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -477,21 +368,23 @@ function appendChild(aDocument, aParent, aTag, aAttributes)
|
|||
/**
|
||||
* Creates a new PropertyPanel.
|
||||
*
|
||||
* @see PropertyTreeView
|
||||
* @param nsIDOMNode aParent
|
||||
* Parent node to append the created panel to.
|
||||
* @param nsIDOMDocument aDocument
|
||||
* Document to create the new nodes on.
|
||||
* @param string aTitle
|
||||
* Title for the panel.
|
||||
* @param string aObject
|
||||
* Object to display in the tree.
|
||||
* Object to display in the tree. For details about this object please
|
||||
* see the PropertyTreeView constructor in this file.
|
||||
* @param array of objects aButtons
|
||||
* Array with buttons to display at the bottom of the panel.
|
||||
*/
|
||||
function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
|
||||
function PropertyPanel(aParent, aTitle, aObject, aButtons)
|
||||
{
|
||||
let document = aParent.ownerDocument;
|
||||
|
||||
// Create the underlying panel
|
||||
this.panel = createElement(aDocument, "panel", {
|
||||
this.panel = createElement(document, "panel", {
|
||||
label: aTitle,
|
||||
titlebar: "normal",
|
||||
noautofocus: "true",
|
||||
|
@ -500,13 +393,13 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
|
|||
});
|
||||
|
||||
// Create the tree.
|
||||
let tree = this.tree = createElement(aDocument, "tree", {
|
||||
let tree = this.tree = createElement(document, "tree", {
|
||||
flex: 1,
|
||||
hidecolumnpicker: "true"
|
||||
});
|
||||
|
||||
let treecols = aDocument.createElement("treecols");
|
||||
appendChild(aDocument, treecols, "treecol", {
|
||||
let treecols = document.createElement("treecols");
|
||||
appendChild(document, treecols, "treecol", {
|
||||
primary: "true",
|
||||
flex: 1,
|
||||
hideheader: "true",
|
||||
|
@ -514,18 +407,18 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
|
|||
});
|
||||
tree.appendChild(treecols);
|
||||
|
||||
tree.appendChild(aDocument.createElement("treechildren"));
|
||||
tree.appendChild(document.createElement("treechildren"));
|
||||
this.panel.appendChild(tree);
|
||||
|
||||
// Create the footer.
|
||||
let footer = createElement(aDocument, "hbox", { align: "end" });
|
||||
appendChild(aDocument, footer, "spacer", { flex: 1 });
|
||||
let footer = createElement(document, "hbox", { align: "end" });
|
||||
appendChild(document, footer, "spacer", { flex: 1 });
|
||||
|
||||
// The footer can have butttons.
|
||||
let self = this;
|
||||
if (aButtons) {
|
||||
aButtons.forEach(function(button) {
|
||||
let buttonNode = appendChild(aDocument, footer, "button", {
|
||||
let buttonNode = appendChild(document, footer, "button", {
|
||||
label: button.label,
|
||||
accesskey: button.accesskey || "",
|
||||
class: button.class || "",
|
||||
|
@ -534,7 +427,7 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
|
|||
});
|
||||
}
|
||||
|
||||
appendChild(aDocument, footer, "resizer", { dir: "bottomend" });
|
||||
appendChild(document, footer, "resizer", { dir: "bottomend" });
|
||||
this.panel.appendChild(footer);
|
||||
|
||||
aParent.appendChild(this.panel);
|
||||
|
@ -559,20 +452,15 @@ function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
|
|||
}
|
||||
|
||||
/**
|
||||
* Destroy the PropertyPanel. This closes the poped up panel and removes
|
||||
* it from the browser DOM.
|
||||
*
|
||||
* @returns void
|
||||
* Destroy the PropertyPanel. This closes the panel and removes it from the
|
||||
* browser DOM.
|
||||
*/
|
||||
PropertyPanel.prototype.destroy = function PP_destroy()
|
||||
{
|
||||
this.treeView.data = null;
|
||||
this.panel.parentNode.removeChild(this.panel);
|
||||
this.treeView = null;
|
||||
this.panel = null;
|
||||
this.tree = null;
|
||||
|
||||
if (this.linkNode) {
|
||||
this.linkNode._panelOpen = false;
|
||||
this.linkNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,466 +0,0 @@
|
|||
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
|
||||
/* 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 = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", function () {
|
||||
let obj = {};
|
||||
Cu.import("resource:///modules/WebConsoleUtils.jsm", obj);
|
||||
return obj.WebConsoleUtils;
|
||||
});
|
||||
|
||||
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// PropertyTreeView.
|
||||
|
||||
/**
|
||||
* This is an implementation of the nsITreeView interface. For comments on the
|
||||
* interface properties, see the documentation:
|
||||
* https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsITreeView
|
||||
*/
|
||||
var PropertyTreeView = function() {
|
||||
this._rows = [];
|
||||
this._objectCache = {};
|
||||
};
|
||||
|
||||
PropertyTreeView.prototype = {
|
||||
/**
|
||||
* Stores the visible rows of the tree.
|
||||
* @private
|
||||
*/
|
||||
_rows: null,
|
||||
|
||||
/**
|
||||
* Stores the nsITreeBoxObject for this tree.
|
||||
* @private
|
||||
*/
|
||||
_treeBox: null,
|
||||
|
||||
/**
|
||||
* Stores cached information about local objects being inspected.
|
||||
* @private
|
||||
*/
|
||||
_objectCache: null,
|
||||
|
||||
/**
|
||||
* Use this setter to update the content of the tree.
|
||||
*
|
||||
* @param object aObject
|
||||
* An object that holds information about the object you want to
|
||||
* display in the property panel. Object properties:
|
||||
* - object:
|
||||
* This is the raw object you want to display. You can only provide
|
||||
* this object if you want the property panel to work in sync mode.
|
||||
* - remoteObject:
|
||||
* An array that holds information on the remote object being
|
||||
* inspected. Each element in this array describes each property in the
|
||||
* remote object. See WebConsoleUtils.namesAndValuesOf() for details.
|
||||
* - rootCacheId:
|
||||
* The cache ID where the objects referenced in remoteObject are found.
|
||||
* - panelCacheId:
|
||||
* The cache ID where any object retrieved by this property panel
|
||||
* instance should be stored into.
|
||||
* - remoteObjectProvider:
|
||||
* A function that is invoked when a new object is needed. This is
|
||||
* called when the user tries to expand an inspectable property. The
|
||||
* callback must take four arguments:
|
||||
* - fromCacheId:
|
||||
* Tells from where to retrieve the object the user picked (from
|
||||
* which cache ID).
|
||||
* - objectId:
|
||||
* The object ID the user wants.
|
||||
* - panelCacheId:
|
||||
* Tells in which cache ID to store the objects referenced by
|
||||
* objectId so they can be retrieved later.
|
||||
* - callback:
|
||||
* The callback function to be invoked when the remote object is
|
||||
* received. This function takes one argument: the raw message
|
||||
* received from the Web Console content script.
|
||||
*/
|
||||
set data(aData) {
|
||||
let oldLen = this._rows.length;
|
||||
|
||||
this._cleanup();
|
||||
|
||||
if (!aData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aData.remoteObject) {
|
||||
this._rootCacheId = aData.rootCacheId;
|
||||
this._panelCacheId = aData.panelCacheId;
|
||||
this._remoteObjectProvider = aData.remoteObjectProvider;
|
||||
this._rows = [].concat(aData.remoteObject);
|
||||
this._updateRemoteObject(this._rows, 0);
|
||||
}
|
||||
else if (aData.object) {
|
||||
this._rows = this._inspectObject(aData.object);
|
||||
}
|
||||
else {
|
||||
throw new Error("First argument must have a .remoteObject or " +
|
||||
"an .object property!");
|
||||
}
|
||||
|
||||
if (this._treeBox) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
if (oldLen) {
|
||||
this._treeBox.rowCountChanged(0, -oldLen);
|
||||
}
|
||||
this._treeBox.rowCountChanged(0, this._rows.length);
|
||||
this._treeBox.endUpdateBatch();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a remote object so it can be used with the tree view. This method
|
||||
* adds properties to each array element.
|
||||
*
|
||||
* @private
|
||||
* @param array aObject
|
||||
* The remote object you want prepared for use with the tree view.
|
||||
* @param number aLevel
|
||||
* The level you want to give to each property in the remote object.
|
||||
*/
|
||||
_updateRemoteObject: function PTV__updateRemoteObject(aObject, aLevel)
|
||||
{
|
||||
aObject.forEach(function(aElement) {
|
||||
aElement.level = aLevel;
|
||||
aElement.isOpened = false;
|
||||
aElement.children = null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Inspect a local object.
|
||||
*
|
||||
* @private
|
||||
* @param object aObject
|
||||
* The object you want to inspect.
|
||||
*/
|
||||
_inspectObject: function PTV__inspectObject(aObject)
|
||||
{
|
||||
this._objectCache = {};
|
||||
this._remoteObjectProvider = this._localObjectProvider.bind(this);
|
||||
let children = WebConsoleUtils.namesAndValuesOf(aObject, this._objectCache);
|
||||
this._updateRemoteObject(children, 0);
|
||||
return children;
|
||||
},
|
||||
|
||||
/**
|
||||
* An object provider for when the user inspects local objects (not remote
|
||||
* ones).
|
||||
*
|
||||
* @private
|
||||
* @param string aFromCacheId
|
||||
* The cache ID from where to retrieve the desired object.
|
||||
* @param string aObjectId
|
||||
* The ID of the object you want.
|
||||
* @param string aDestCacheId
|
||||
* The ID of the cache where to store any objects referenced by the
|
||||
* desired object.
|
||||
* @param function aCallback
|
||||
* The function you want to receive the object.
|
||||
*/
|
||||
_localObjectProvider:
|
||||
function PTV__localObjectProvider(aFromCacheId, aObjectId, aDestCacheId,
|
||||
aCallback)
|
||||
{
|
||||
let object = WebConsoleUtils.namesAndValuesOf(this._objectCache[aObjectId],
|
||||
this._objectCache);
|
||||
aCallback({cacheId: aFromCacheId,
|
||||
objectId: aObjectId,
|
||||
object: object,
|
||||
childrenCacheId: aDestCacheId || aFromCacheId,
|
||||
});
|
||||
},
|
||||
|
||||
/** nsITreeView interface implementation **/
|
||||
|
||||
selection: null,
|
||||
|
||||
get rowCount() { return this._rows.length; },
|
||||
setTree: function(treeBox) { this._treeBox = treeBox; },
|
||||
getCellText: function(idx, column) {
|
||||
let row = this._rows[idx];
|
||||
return row.name + ": " + row.value;
|
||||
},
|
||||
getLevel: function(idx) {
|
||||
return this._rows[idx].level;
|
||||
},
|
||||
isContainer: function(idx) {
|
||||
return !!this._rows[idx].inspectable;
|
||||
},
|
||||
isContainerOpen: function(idx) {
|
||||
return this._rows[idx].isOpened;
|
||||
},
|
||||
isContainerEmpty: function(idx) { return false; },
|
||||
isSeparator: function(idx) { return false; },
|
||||
isSorted: function() { return false; },
|
||||
isEditable: function(idx, column) { return false; },
|
||||
isSelectable: function(row, col) { return true; },
|
||||
|
||||
getParentIndex: function(idx)
|
||||
{
|
||||
if (this.getLevel(idx) == 0) {
|
||||
return -1;
|
||||
}
|
||||
for (var t = idx - 1; t >= 0; t--) {
|
||||
if (this.isContainer(t)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
hasNextSibling: function(idx, after)
|
||||
{
|
||||
var thisLevel = this.getLevel(idx);
|
||||
return this._rows.slice(after + 1).some(function (r) r.level == thisLevel);
|
||||
},
|
||||
|
||||
toggleOpenState: function(idx)
|
||||
{
|
||||
let item = this._rows[idx];
|
||||
if (!item.inspectable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.isOpened) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
item.isOpened = false;
|
||||
|
||||
var thisLevel = item.level;
|
||||
var t = idx + 1, deleteCount = 0;
|
||||
while (t < this._rows.length && this.getLevel(t++) > thisLevel) {
|
||||
deleteCount++;
|
||||
}
|
||||
|
||||
if (deleteCount) {
|
||||
this._rows.splice(idx + 1, deleteCount);
|
||||
this._treeBox.rowCountChanged(idx + 1, -deleteCount);
|
||||
}
|
||||
this._treeBox.invalidateRow(idx);
|
||||
this._treeBox.endUpdateBatch();
|
||||
}
|
||||
else {
|
||||
let levelUpdate = true;
|
||||
let callback = function _onRemoteResponse(aResponse) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
item.isOpened = true;
|
||||
|
||||
if (levelUpdate) {
|
||||
this._updateRemoteObject(aResponse.object, item.level + 1);
|
||||
item.children = aResponse.object;
|
||||
}
|
||||
|
||||
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item.children));
|
||||
|
||||
this._treeBox.rowCountChanged(idx + 1, item.children.length);
|
||||
this._treeBox.invalidateRow(idx);
|
||||
this._treeBox.endUpdateBatch();
|
||||
}.bind(this);
|
||||
|
||||
if (!item.children) {
|
||||
let fromCacheId = item.level > 0 ? this._panelCacheId :
|
||||
this._rootCacheId;
|
||||
this._remoteObjectProvider(fromCacheId, item.objectId,
|
||||
this._panelCacheId, callback);
|
||||
}
|
||||
else {
|
||||
levelUpdate = false;
|
||||
callback({object: item.children});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getImageSrc: function(idx, column) { },
|
||||
getProgressMode : function(idx,column) { },
|
||||
getCellValue: function(idx, column) { },
|
||||
cycleHeader: function(col, elem) { },
|
||||
selectionChanged: function() { },
|
||||
cycleCell: function(idx, column) { },
|
||||
performAction: function(action) { },
|
||||
performActionOnCell: function(action, index, column) { },
|
||||
performActionOnRow: function(action, row) { },
|
||||
getRowProperties: function(idx, column, prop) { },
|
||||
getCellProperties: function(idx, column, prop) { },
|
||||
getColumnProperties: function(column, element, prop) { },
|
||||
|
||||
setCellValue: function(row, col, value) { },
|
||||
setCellText: function(row, col, value) { },
|
||||
drop: function(index, orientation, dataTransfer) { },
|
||||
canDrop: function(index, orientation, dataTransfer) { return false; },
|
||||
|
||||
_cleanup: function PTV__cleanup()
|
||||
{
|
||||
if (this._rows.length) {
|
||||
// Reset the existing _rows children to the initial state.
|
||||
this._updateRemoteObject(this._rows, 0);
|
||||
this._rows = [];
|
||||
}
|
||||
|
||||
delete this._objectCache;
|
||||
delete this._rootCacheId;
|
||||
delete this._panelCacheId;
|
||||
delete this._remoteObjectProvider;
|
||||
},
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// Helper for creating the panel.
|
||||
|
||||
/**
|
||||
* Creates a DOMNode and sets all the attributes of aAttributes on the created
|
||||
* element.
|
||||
*
|
||||
* @param nsIDOMDocument aDocument
|
||||
* Document to create the new DOMNode.
|
||||
* @param string aTag
|
||||
* Name of the tag for the DOMNode.
|
||||
* @param object aAttributes
|
||||
* Attributes set on the created DOMNode.
|
||||
* @returns nsIDOMNode
|
||||
*/
|
||||
function createElement(aDocument, aTag, aAttributes)
|
||||
{
|
||||
let node = aDocument.createElement(aTag);
|
||||
for (var attr in aAttributes) {
|
||||
node.setAttribute(attr, aAttributes[attr]);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DOMNode and appends it to aParent.
|
||||
*
|
||||
* @param nsIDOMDocument aDocument
|
||||
* Document to create the new DOMNode.
|
||||
* @param nsIDOMNode aParent
|
||||
* A parent node to append the created element.
|
||||
* @param string aTag
|
||||
* Name of the tag for the DOMNode.
|
||||
* @param object aAttributes
|
||||
* Attributes set on the created DOMNode.
|
||||
* @returns nsIDOMNode
|
||||
*/
|
||||
function appendChild(aDocument, aParent, aTag, aAttributes)
|
||||
{
|
||||
let node = createElement(aDocument, aTag, aAttributes);
|
||||
aParent.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// PropertyPanel
|
||||
|
||||
/**
|
||||
* Creates a new PropertyPanel.
|
||||
*
|
||||
* @see PropertyTreeView
|
||||
* @param nsIDOMNode aParent
|
||||
* Parent node to append the created panel to.
|
||||
* @param string aTitle
|
||||
* Title for the panel.
|
||||
* @param string aObject
|
||||
* Object to display in the tree. For details about this object please
|
||||
* see the PropertyTreeView.data property in this file.
|
||||
* @param array of objects aButtons
|
||||
* Array with buttons to display at the bottom of the panel.
|
||||
*/
|
||||
function PropertyPanel(aParent, aTitle, aObject, aButtons)
|
||||
{
|
||||
let document = aParent.ownerDocument;
|
||||
|
||||
// Create the underlying panel
|
||||
this.panel = createElement(document, "panel", {
|
||||
label: aTitle,
|
||||
titlebar: "normal",
|
||||
noautofocus: "true",
|
||||
noautohide: "true",
|
||||
close: "true",
|
||||
});
|
||||
|
||||
// Create the tree.
|
||||
let tree = this.tree = createElement(document, "tree", {
|
||||
flex: 1,
|
||||
hidecolumnpicker: "true"
|
||||
});
|
||||
|
||||
let treecols = document.createElement("treecols");
|
||||
appendChild(document, treecols, "treecol", {
|
||||
primary: "true",
|
||||
flex: 1,
|
||||
hideheader: "true",
|
||||
ignoreincolumnpicker: "true"
|
||||
});
|
||||
tree.appendChild(treecols);
|
||||
|
||||
tree.appendChild(document.createElement("treechildren"));
|
||||
this.panel.appendChild(tree);
|
||||
|
||||
// Create the footer.
|
||||
let footer = createElement(document, "hbox", { align: "end" });
|
||||
appendChild(document, footer, "spacer", { flex: 1 });
|
||||
|
||||
// The footer can have butttons.
|
||||
let self = this;
|
||||
if (aButtons) {
|
||||
aButtons.forEach(function(button) {
|
||||
let buttonNode = appendChild(document, footer, "button", {
|
||||
label: button.label,
|
||||
accesskey: button.accesskey || "",
|
||||
class: button.class || "",
|
||||
});
|
||||
buttonNode.addEventListener("command", button.oncommand, false);
|
||||
});
|
||||
}
|
||||
|
||||
appendChild(document, footer, "resizer", { dir: "bottomend" });
|
||||
this.panel.appendChild(footer);
|
||||
|
||||
aParent.appendChild(this.panel);
|
||||
|
||||
// Create the treeView object.
|
||||
this.treeView = new PropertyTreeView();
|
||||
this.treeView.data = aObject;
|
||||
|
||||
// Set the treeView object on the tree view. This has to be done *after* the
|
||||
// panel is shown. This is because the tree binding must be attached first.
|
||||
this.panel.addEventListener("popupshown", function onPopupShow()
|
||||
{
|
||||
self.panel.removeEventListener("popupshown", onPopupShow, false);
|
||||
self.tree.view = self.treeView;
|
||||
}, false);
|
||||
|
||||
this.panel.addEventListener("popuphidden", function onPopupHide()
|
||||
{
|
||||
self.panel.removeEventListener("popuphidden", onPopupHide, false);
|
||||
self.destroy();
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the PropertyPanel. This closes the panel and removes it from the
|
||||
* browser DOM.
|
||||
*/
|
||||
PropertyPanel.prototype.destroy = function PP_destroy()
|
||||
{
|
||||
this.treeView.data = null;
|
||||
this.panel.parentNode.removeChild(this.panel);
|
||||
this.treeView = null;
|
||||
this.panel = null;
|
||||
this.tree = null;
|
||||
}
|
||||
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ["WebConsoleUtils"];
|
||||
var EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider"];
|
||||
|
||||
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
|
||||
|
||||
|
@ -403,7 +403,7 @@ var WebConsoleUtils = {
|
|||
let m = /^\[object (\S+)\]/.exec(presentable);
|
||||
|
||||
try {
|
||||
if (type == "object" && typeof aObject.next == "function" &&
|
||||
if (typeof aObject == "object" && typeof aObject.next == "function" &&
|
||||
m && m[1] == "Generator") {
|
||||
return {
|
||||
type: TYPES.GENERATOR,
|
||||
|
@ -419,7 +419,8 @@ var WebConsoleUtils = {
|
|||
};
|
||||
}
|
||||
|
||||
if (type == "object" && typeof aObject.__iterator__ == "function") {
|
||||
if (typeof aObject == "object" &&
|
||||
typeof aObject.__iterator__ == "function") {
|
||||
return {
|
||||
type: TYPES.ITERATOR,
|
||||
display: "Iterator"
|
||||
|
@ -514,10 +515,11 @@ var WebConsoleUtils = {
|
|||
let value, presentable;
|
||||
|
||||
let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
|
||||
let deprecated = ["width", "height", "inputEncoding"];
|
||||
|
||||
for (let propName in aObject) {
|
||||
// See bug 632275: skip deprecated width and height properties.
|
||||
if (isDOMDocument && (propName == "width" || propName == "height")) {
|
||||
// See bug 632275: skip deprecated properties.
|
||||
if (isDOMDocument && deprecated.indexOf(propName) > -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -527,9 +529,14 @@ var WebConsoleUtils = {
|
|||
presentable = {type: TYPES.GETTER, display: "Getter"};
|
||||
}
|
||||
else {
|
||||
try {
|
||||
value = aObject[propName];
|
||||
presentable = this.presentableValueFor(value);
|
||||
}
|
||||
catch (ex) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let pair = {};
|
||||
pair.name = propName;
|
||||
|
@ -704,3 +711,228 @@ WebConsoleUtils.l10n = {
|
|||
XPCOMUtils.defineLazyGetter(WebConsoleUtils.l10n, "stringBundle", function() {
|
||||
return Services.strings.createBundle(STRINGS_URI);
|
||||
});
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// JS Completer
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var JSPropertyProvider = (function _JSPP(WCU) {
|
||||
const STATE_NORMAL = 0;
|
||||
const STATE_QUOTE = 2;
|
||||
const STATE_DQUOTE = 3;
|
||||
|
||||
const OPEN_BODY = "{[(".split("");
|
||||
const CLOSE_BODY = "}])".split("");
|
||||
const OPEN_CLOSE_BODY = {
|
||||
"{": "}",
|
||||
"[": "]",
|
||||
"(": ")",
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyses a given string to find the last statement that is interesting for
|
||||
* later completion.
|
||||
*
|
||||
* @param string aStr
|
||||
* A string to analyse.
|
||||
*
|
||||
* @returns object
|
||||
* If there was an error in the string detected, then a object like
|
||||
*
|
||||
* { err: "ErrorMesssage" }
|
||||
*
|
||||
* is returned, otherwise a object like
|
||||
*
|
||||
* {
|
||||
* state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
|
||||
* startPos: index of where the last statement begins
|
||||
* }
|
||||
*/
|
||||
function findCompletionBeginning(aStr)
|
||||
{
|
||||
let bodyStack = [];
|
||||
|
||||
let state = STATE_NORMAL;
|
||||
let start = 0;
|
||||
let c;
|
||||
for (let i = 0; i < aStr.length; i++) {
|
||||
c = aStr[i];
|
||||
|
||||
switch (state) {
|
||||
// Normal JS state.
|
||||
case STATE_NORMAL:
|
||||
if (c == '"') {
|
||||
state = STATE_DQUOTE;
|
||||
}
|
||||
else if (c == "'") {
|
||||
state = STATE_QUOTE;
|
||||
}
|
||||
else if (c == ";") {
|
||||
start = i + 1;
|
||||
}
|
||||
else if (c == " ") {
|
||||
start = i + 1;
|
||||
}
|
||||
else if (OPEN_BODY.indexOf(c) != -1) {
|
||||
bodyStack.push({
|
||||
token: c,
|
||||
start: start
|
||||
});
|
||||
start = i + 1;
|
||||
}
|
||||
else if (CLOSE_BODY.indexOf(c) != -1) {
|
||||
var last = bodyStack.pop();
|
||||
if (!last || OPEN_CLOSE_BODY[last.token] != c) {
|
||||
return {
|
||||
err: "syntax error"
|
||||
};
|
||||
}
|
||||
if (c == "}") {
|
||||
start = i + 1;
|
||||
}
|
||||
else {
|
||||
start = last.start;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Double quote state > " <
|
||||
case STATE_DQUOTE:
|
||||
if (c == "\\") {
|
||||
i++;
|
||||
}
|
||||
else if (c == "\n") {
|
||||
return {
|
||||
err: "unterminated string literal"
|
||||
};
|
||||
}
|
||||
else if (c == '"') {
|
||||
state = STATE_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
// Single quote state > ' <
|
||||
case STATE_QUOTE:
|
||||
if (c == "\\") {
|
||||
i++;
|
||||
}
|
||||
else if (c == "\n") {
|
||||
return {
|
||||
err: "unterminated string literal"
|
||||
};
|
||||
}
|
||||
else if (c == "'") {
|
||||
state = STATE_NORMAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state: state,
|
||||
startPos: start
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of properties, that are possible matches based on the passed
|
||||
* scope and inputValue.
|
||||
*
|
||||
* @param object aScope
|
||||
* Scope to use for the completion.
|
||||
*
|
||||
* @param string aInputValue
|
||||
* Value that should be completed.
|
||||
*
|
||||
* @returns null or object
|
||||
* If no completion valued could be computed, null is returned,
|
||||
* otherwise a object with the following form is returned:
|
||||
* {
|
||||
* matches: [ string, string, string ],
|
||||
* matchProp: Last part of the inputValue that was used to find
|
||||
* the matches-strings.
|
||||
* }
|
||||
*/
|
||||
function JSPropertyProvider(aScope, aInputValue)
|
||||
{
|
||||
let obj = WCU.unwrap(aScope);
|
||||
|
||||
// Analyse the aInputValue and find the beginning of the last part that
|
||||
// should be completed.
|
||||
let beginning = findCompletionBeginning(aInputValue);
|
||||
|
||||
// There was an error analysing the string.
|
||||
if (beginning.err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the current state is not STATE_NORMAL, then we are inside of an string
|
||||
// which means that no completion is possible.
|
||||
if (beginning.state != STATE_NORMAL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let completionPart = aInputValue.substring(beginning.startPos);
|
||||
|
||||
// Don't complete on just an empty string.
|
||||
if (completionPart.trim() == "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
let properties = completionPart.split(".");
|
||||
let matchProp;
|
||||
if (properties.length > 1) {
|
||||
matchProp = properties.pop().trimLeft();
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
let prop = properties[i].trim();
|
||||
|
||||
// If obj is undefined or null, then there is no chance to run completion
|
||||
// on it. Exit here.
|
||||
if (typeof obj === "undefined" || obj === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if prop is a getter function on obj. Functions can change other
|
||||
// stuff so we can't execute them to get the next object. Stop here.
|
||||
if (WCU.isNonNativeGetter(obj, prop)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
obj = obj[prop];
|
||||
}
|
||||
catch (ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
matchProp = properties[0].trimLeft();
|
||||
}
|
||||
|
||||
// If obj is undefined or null, then there is no chance to run
|
||||
// completion on it. Exit here.
|
||||
if (typeof obj === "undefined" || obj === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Skip Iterators and Generators.
|
||||
if (WCU.isIteratorOrGenerator(obj)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let matches = [];
|
||||
for (let prop in obj) {
|
||||
if (prop.indexOf(matchProp) == 0) {
|
||||
matches.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
matchProp: matchProp,
|
||||
matches: matches.sort(),
|
||||
};
|
||||
}
|
||||
|
||||
return JSPropertyProvider;
|
||||
})(WebConsoleUtils);
|
||||
|
|
|
@ -8,12 +8,14 @@ let HUD;
|
|||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", tabLoaded, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function tabLoaded() {
|
||||
browser.removeEventListener("load", tabLoaded, true);
|
||||
openConsole();
|
||||
function consoleOpened(aHud) {
|
||||
HUD = aHud;
|
||||
|
||||
content.wrappedJSObject.foobarBug585991 = {
|
||||
"item0": "value0",
|
||||
|
@ -22,16 +24,14 @@ function tabLoaded() {
|
|||
"item3": "value3",
|
||||
};
|
||||
|
||||
let hudId = HUDService.getHudIdByWindow(content);
|
||||
HUD = HUDService.hudReferences[hudId];
|
||||
let jsterm = HUD.jsterm;
|
||||
let popup = jsterm.autocompletePopup;
|
||||
let completeNode = jsterm.completeNode;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
|
||||
popup._panel.addEventListener("popupshown", function() {
|
||||
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||
popup._panel.addEventListener("popupshown", function onShown() {
|
||||
popup._panel.removeEventListener("popupshown", onShown, false);
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
|
@ -79,7 +79,7 @@ function autocompletePopupHidden()
|
|||
let completeNode = jsterm.completeNode;
|
||||
let inputNode = jsterm.inputNode;
|
||||
|
||||
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
|
||||
popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
|
||||
|
@ -88,8 +88,8 @@ function autocompletePopupHidden()
|
|||
|
||||
ok(!completeNode.value, "completeNode is empty");
|
||||
|
||||
popup._panel.addEventListener("popupshown", function() {
|
||||
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||
popup._panel.addEventListener("popupshown", function onShown() {
|
||||
popup._panel.removeEventListener("popupshown", onShown, false);
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
|
@ -104,8 +104,8 @@ function autocompletePopupHidden()
|
|||
is(popup.selectedItem.label, "item0", "item0 is selected");
|
||||
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
|
||||
|
||||
popup._panel.addEventListener("popuphidden", function() {
|
||||
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
|
||||
popup._panel.addEventListener("popuphidden", function onHidden() {
|
||||
popup._panel.removeEventListener("popuphidden", onHidden, false);
|
||||
|
||||
ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
|
||||
|
||||
|
@ -135,8 +135,8 @@ function testReturnKey()
|
|||
let completeNode = jsterm.completeNode;
|
||||
let inputNode = jsterm.inputNode;
|
||||
|
||||
popup._panel.addEventListener("popupshown", function() {
|
||||
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||
popup._panel.addEventListener("popupshown", function onShown() {
|
||||
popup._panel.removeEventListener("popupshown", onShown, false);
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
|
@ -157,8 +157,8 @@ function testReturnKey()
|
|||
is(popup.selectedItem.label, "item1", "item1 is selected");
|
||||
is(completeNode.value, prefix + "item1", "completeNode.value holds item1");
|
||||
|
||||
popup._panel.addEventListener("popuphidden", function() {
|
||||
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
|
||||
popup._panel.addEventListener("popuphidden", function onHidden() {
|
||||
popup._panel.removeEventListener("popuphidden", onHidden, false);
|
||||
|
||||
ok(!popup.isOpen, "popup is not open after VK_RETURN");
|
||||
|
||||
|
|
|
@ -13,12 +13,8 @@ let HUD;
|
|||
|
||||
let outputItem;
|
||||
|
||||
function tabLoad1(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
|
||||
openConsole();
|
||||
|
||||
HUD = HUDService.getHudByWindow(content);
|
||||
function consoleOpened(aHud) {
|
||||
HUD = aHud;
|
||||
|
||||
outputNode = HUD.outputNode;
|
||||
|
||||
|
@ -26,11 +22,10 @@ function tabLoad1(aEvent) {
|
|||
|
||||
// Reload so we get some output in the console.
|
||||
browser.contentWindow.location.reload();
|
||||
log(document);
|
||||
}
|
||||
|
||||
function tabLoad2(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
browser.removeEventListener(aEvent.type, tabLoad2, true);
|
||||
|
||||
outputItem = outputNode.querySelector(".hud-networkinfo .hud-clickable");
|
||||
ok(outputItem, "found a network message");
|
||||
|
@ -42,7 +37,7 @@ function tabLoad2(aEvent) {
|
|||
}
|
||||
|
||||
function networkPanelShown(aEvent) {
|
||||
document.removeEventListener(aEvent.type, arguments.callee, false);
|
||||
document.removeEventListener(aEvent.type, networkPanelShown, false);
|
||||
|
||||
document.addEventListener("popupshown", networkPanelShowFailure, false);
|
||||
|
||||
|
@ -57,13 +52,13 @@ function networkPanelShown(aEvent) {
|
|||
}
|
||||
|
||||
function networkPanelShowFailure(aEvent) {
|
||||
document.removeEventListener(aEvent.type, arguments.callee, false);
|
||||
document.removeEventListener(aEvent.type, networkPanelShowFailure, false);
|
||||
|
||||
ok(false, "the network panel should not show");
|
||||
}
|
||||
|
||||
function networkPanelHidden(aEvent) {
|
||||
this.removeEventListener(aEvent.type, arguments.callee, false);
|
||||
this.removeEventListener(aEvent.type, networkPanelHidden, false);
|
||||
|
||||
// The network panel should not show because this is a mouse event that starts
|
||||
// in a position and ends in another.
|
||||
|
@ -92,20 +87,27 @@ function networkPanelHidden(aEvent) {
|
|||
HUD.jsterm.setInputValue("document");
|
||||
HUD.jsterm.execute();
|
||||
|
||||
outputItem = outputNode.querySelector(".webconsole-msg-output " +
|
||||
".hud-clickable");
|
||||
ok(outputItem, "found a jsterm output message");
|
||||
|
||||
document.addEventListener("popupshown", properyPanelShown, false);
|
||||
waitForSuccess({
|
||||
name: "jsterm output message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelector(".webconsole-msg-output .hud-clickable");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
document.addEventListener("popupshown", propertyPanelShown, false);
|
||||
|
||||
// Send the mousedown and click events such that the property panel opens.
|
||||
EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
|
||||
EventUtils.sendMouseEvent({type: "click"}, outputItem);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function properyPanelShown(aEvent) {
|
||||
document.removeEventListener(aEvent.type, arguments.callee, false);
|
||||
function propertyPanelShown(aEvent) {
|
||||
document.removeEventListener(aEvent.type, propertyPanelShown, false);
|
||||
|
||||
document.addEventListener("popupshown", propertyPanelShowFailure, false);
|
||||
|
||||
|
@ -120,13 +122,13 @@ function properyPanelShown(aEvent) {
|
|||
}
|
||||
|
||||
function propertyPanelShowFailure(aEvent) {
|
||||
document.removeEventListener(aEvent.type, arguments.callee, false);
|
||||
document.removeEventListener(aEvent.type, propertyPanelShowFailure, false);
|
||||
|
||||
ok(false, "the property panel should not show");
|
||||
}
|
||||
|
||||
function propertyPanelHidden(aEvent) {
|
||||
this.removeEventListener(aEvent.type, arguments.callee, false);
|
||||
this.removeEventListener(aEvent.type, propertyPanelHidden, false);
|
||||
|
||||
// The property panel should not show because this is a mouse event that
|
||||
// starts in a position and ends in another.
|
||||
|
@ -149,13 +151,16 @@ function propertyPanelHidden(aEvent) {
|
|||
|
||||
executeSoon(function() {
|
||||
document.removeEventListener("popupshown", propertyPanelShowFailure, false);
|
||||
outputItem = null;
|
||||
finishTest();
|
||||
HUD = outputItem = null;
|
||||
executeSoon(finishTest);
|
||||
});
|
||||
}
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", tabLoad1, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -169,6 +169,18 @@ function testGen() {
|
|||
HUD.jsterm.setInputValue("print(" + inputValue + ")");
|
||||
HUD.jsterm.execute();
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm print() output for test #" + cpos,
|
||||
validatorFn: function()
|
||||
{
|
||||
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
|
||||
},
|
||||
successFn: subtestNext,
|
||||
failureFn: testNext,
|
||||
});
|
||||
|
||||
yield;
|
||||
|
||||
outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
|
||||
"last-child");
|
||||
ok(outputItem,
|
||||
|
@ -178,6 +190,32 @@ function testGen() {
|
|||
|
||||
// Test jsterm execution output.
|
||||
|
||||
HUD.jsterm.clearOutput();
|
||||
HUD.jsterm.setInputValue(inputValue);
|
||||
HUD.jsterm.execute();
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm output for test #" + cpos,
|
||||
validatorFn: function()
|
||||
{
|
||||
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
|
||||
},
|
||||
successFn: subtestNext,
|
||||
failureFn: testNext,
|
||||
});
|
||||
|
||||
yield;
|
||||
|
||||
outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
|
||||
"last-child");
|
||||
ok(outputItem, "found the jsterm output line for inputValues[" + cpos + "]");
|
||||
ok(outputItem.textContent.indexOf(expectedOutput) > -1,
|
||||
"jsterm output is correct for inputValues[" + cpos + "]");
|
||||
|
||||
let messageBody = outputItem.querySelector(".webconsole-msg-body");
|
||||
ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
|
||||
|
||||
// Test click on output.
|
||||
let eventHandlerID = eventHandlers.length + 1;
|
||||
|
||||
let propertyPanelShown = function(aEvent) {
|
||||
|
@ -205,19 +243,6 @@ function testGen() {
|
|||
|
||||
eventHandlers.push(propertyPanelShown);
|
||||
|
||||
HUD.jsterm.clearOutput();
|
||||
HUD.jsterm.setInputValue(inputValue);
|
||||
HUD.jsterm.execute();
|
||||
|
||||
outputItem = HUD.outputNode.querySelector(".webconsole-msg-output:" +
|
||||
"last-child");
|
||||
ok(outputItem, "found the jsterm output line for inputValues[" + cpos + "]");
|
||||
ok(outputItem.textContent.indexOf(expectedOutput) > -1,
|
||||
"jsterm output is correct for inputValues[" + cpos + "]");
|
||||
|
||||
let messageBody = outputItem.querySelector(".webconsole-msg-body");
|
||||
ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
|
||||
|
||||
// Send the mousedown, mouseup and click events to check if the property
|
||||
// panel opens.
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, messageBody, window);
|
||||
|
@ -251,7 +276,7 @@ function testEnd() {
|
|||
}
|
||||
}
|
||||
|
||||
testDriver = null;
|
||||
HUD = inputValues = testDriver = null;
|
||||
executeSoon(finishTest);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,12 +7,13 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
|
|||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function() {
|
||||
browser.removeEventListener("load", arguments.callee, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
openConsole();
|
||||
openConsole(null, function() {
|
||||
content.location.reload();
|
||||
browser.addEventListener("load", tabLoaded, true);
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -30,9 +31,6 @@ function tabLoaded() {
|
|||
let networkLink = networkMessage.querySelector(".webconsole-msg-link");
|
||||
ok(networkLink, "found network message link");
|
||||
|
||||
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
ok(jstermMessage, "found output message");
|
||||
|
||||
let popupset = document.getElementById("mainPopupSet");
|
||||
ok(popupset, "found #mainPopupSet");
|
||||
|
||||
|
@ -85,6 +83,18 @@ function tabLoaded() {
|
|||
}
|
||||
});
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm output message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
|
||||
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -46,9 +46,6 @@ function tabLoaded() {
|
|||
let networkLink = networkMessage.querySelector(".webconsole-msg-link");
|
||||
ok(networkLink, "found network message link");
|
||||
|
||||
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
ok(jstermMessage, "found output message");
|
||||
|
||||
let popupset = document.getElementById("mainPopupSet");
|
||||
ok(popupset, "found #mainPopupSet");
|
||||
|
||||
|
@ -112,8 +109,20 @@ function tabLoaded() {
|
|||
});
|
||||
|
||||
// Show the network and object inspector panels.
|
||||
waitForSuccess({
|
||||
name: "jsterm output message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
|
||||
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function togglePBAndThen(callback) {
|
||||
|
|
|
@ -9,35 +9,60 @@
|
|||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html";
|
||||
|
||||
function tabLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, tabLoad, true);
|
||||
|
||||
openConsole(null, function(HUD) {
|
||||
function test$(HUD) {
|
||||
HUD.jsterm.clearOutput();
|
||||
|
||||
HUD.jsterm.setInputValue("$(document.body)");
|
||||
HUD.jsterm.execute();
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm output for $()",
|
||||
validatorFn: function()
|
||||
{
|
||||
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let outputItem = HUD.outputNode.
|
||||
querySelector(".webconsole-msg-output:last-child");
|
||||
ok(outputItem.textContent.indexOf("<p>") > -1,
|
||||
"jsterm output is correct for $()");
|
||||
|
||||
test$$(HUD);
|
||||
},
|
||||
failureFn: test$$.bind(null, HUD),
|
||||
});
|
||||
}
|
||||
|
||||
function test$$(HUD) {
|
||||
HUD.jsterm.clearOutput();
|
||||
|
||||
HUD.jsterm.setInputValue("$$(document)");
|
||||
HUD.jsterm.execute();
|
||||
|
||||
outputItem = HUD.outputNode.
|
||||
waitForSuccess({
|
||||
name: "jsterm output for $$()",
|
||||
validatorFn: function()
|
||||
{
|
||||
return HUD.outputNode.querySelector(".webconsole-msg-output:last-child");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let outputItem = HUD.outputNode.
|
||||
querySelector(".webconsole-msg-output:last-child");
|
||||
ok(outputItem.textContent.indexOf("621644") > -1,
|
||||
"jsterm output is correct for $$()");
|
||||
|
||||
executeSoon(finishTest);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", tabLoad, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, test$);
|
||||
}, true);
|
||||
}
|
||||
|
|
|
@ -5,26 +5,27 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
|
|||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", tabLoaded, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function tabLoaded() {
|
||||
browser.removeEventListener("load", tabLoaded, true);
|
||||
openConsole();
|
||||
|
||||
let hudId = HUDService.getHudIdByWindow(content);
|
||||
let HUD = HUDService.hudReferences[hudId];
|
||||
function consoleOpened(HUD) {
|
||||
let jsterm = HUD.jsterm;
|
||||
|
||||
let doc = content.wrappedJSObject.document;
|
||||
|
||||
let panel = jsterm.openPropertyPanel("Test1", doc);
|
||||
let panel = jsterm.openPropertyPanel({ data: { object: doc }});
|
||||
|
||||
let rows = panel.treeView._rows;
|
||||
let view = panel.treeView;
|
||||
let find = function(regex) {
|
||||
return rows.some(function(row) {
|
||||
return regex.test(row.display);
|
||||
});
|
||||
for (let i = 0; i < view.rowCount; i++) {
|
||||
if (regex.test(view.getCellText(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
ok(!find(/^(width|height):/), "no document.width/height");
|
||||
|
@ -33,8 +34,8 @@ function tabLoaded() {
|
|||
|
||||
let getterValue = doc.foobar._val;
|
||||
|
||||
panel = jsterm.openPropertyPanel("Test2", doc.foobar);
|
||||
rows = panel.treeView._rows;
|
||||
panel = jsterm.openPropertyPanel({ data: { object: doc.foobar }});
|
||||
view = panel.treeView;
|
||||
|
||||
is(getterValue, doc.foobar._val, "getter did not execute");
|
||||
is(getterValue+1, doc.foobar.val, "getter executed");
|
||||
|
|
|
@ -7,27 +7,25 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
|
|||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", tabLoaded, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function tabLoaded() {
|
||||
browser.removeEventListener("load", tabLoaded, true);
|
||||
|
||||
function consoleOpened(HUD) {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
|
||||
let WCU = tmp.WebConsoleUtils;
|
||||
let JSPropertyProvider = tmp.JSPropertyProvider;
|
||||
tmp = null;
|
||||
|
||||
openConsole();
|
||||
|
||||
let hudId = HUDService.getHudIdByWindow(content);
|
||||
let HUD = HUDService.hudReferences[hudId];
|
||||
let jsterm = HUD.jsterm;
|
||||
|
||||
let win = content.wrappedJSObject;
|
||||
|
||||
// Make sure autocomplete does not walk through iterators and generators.
|
||||
let result = win.gen1.next();
|
||||
let completion = jsterm.propertyProvider(win, "gen1.");
|
||||
let completion = JSPropertyProvider(win, "gen1.");
|
||||
is(completion, null, "no matchees for gen1");
|
||||
ok(!WCU.isObjectInspectable(win.gen1),
|
||||
"gen1 is not inspectable");
|
||||
|
@ -36,7 +34,7 @@ function tabLoaded() {
|
|||
|
||||
result = win.gen2.next();
|
||||
|
||||
completion = jsterm.propertyProvider(win, "gen2.");
|
||||
completion = JSPropertyProvider(win, "gen2.");
|
||||
is(completion, null, "no matchees for gen2");
|
||||
ok(!WCU.isObjectInspectable(win.gen2),
|
||||
"gen2 is not inspectable");
|
||||
|
@ -48,7 +46,7 @@ function tabLoaded() {
|
|||
is(result[0], "foo", "iter1.next() [0] is correct");
|
||||
is(result[1], "bar", "iter1.next() [1] is correct");
|
||||
|
||||
completion = jsterm.propertyProvider(win, "iter1.");
|
||||
completion = JSPropertyProvider(win, "iter1.");
|
||||
is(completion, null, "no matchees for iter1");
|
||||
ok(!WCU.isObjectInspectable(win.iter1),
|
||||
"iter1 is not inspectable");
|
||||
|
@ -57,27 +55,57 @@ function tabLoaded() {
|
|||
is(result[0], "baz", "iter1.next() [0] is correct");
|
||||
is(result[1], "baaz", "iter1.next() [1] is correct");
|
||||
|
||||
completion = jsterm.propertyProvider(content, "iter2.");
|
||||
completion = JSPropertyProvider(content, "iter2.");
|
||||
is(completion, null, "no matchees for iter2");
|
||||
ok(!WCU.isObjectInspectable(win.iter2),
|
||||
"iter2 is not inspectable");
|
||||
|
||||
completion = jsterm.propertyProvider(win, "window.");
|
||||
completion = JSPropertyProvider(win, "window.");
|
||||
ok(completion, "matches available for window");
|
||||
ok(completion.matches.length, "matches available for window (length)");
|
||||
ok(WCU.isObjectInspectable(win),
|
||||
"window is inspectable");
|
||||
|
||||
let panel = jsterm.openPropertyPanel("Test", win);
|
||||
ok(panel, "opened the Property Panel");
|
||||
let rows = panel.treeView._rows;
|
||||
ok(rows.length, "Property Panel rows are available");
|
||||
jsterm.clearOutput();
|
||||
|
||||
jsterm.setInputValue("window");
|
||||
jsterm.execute();
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm window object output",
|
||||
validatorFn: function()
|
||||
{
|
||||
return HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
document.addEventListener("popupshown", function onShown(aEvent) {
|
||||
document.removeEventListener("popupshown", onShown, false);
|
||||
executeSoon(testPropertyPanel.bind(null, aEvent.target));
|
||||
}, false);
|
||||
|
||||
let node = HUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
EventUtils.synthesizeMouse(node, 2, 2, {});
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function testPropertyPanel(aPanel) {
|
||||
let tree = aPanel.querySelector("tree");
|
||||
let view = tree.view;
|
||||
let col = tree.columns[0];
|
||||
ok(view.rowCount, "Property Panel rowCount");
|
||||
|
||||
let find = function(display, children) {
|
||||
return rows.some(function(row) {
|
||||
return row.display == display &&
|
||||
row.children == children;
|
||||
});
|
||||
for (let i = 0; i < view.rowCount; i++) {
|
||||
if (view.isContainer(i) == children &&
|
||||
view.getCellText(i, col) == display) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
ok(find("gen1: Generator", false),
|
||||
|
@ -92,13 +120,5 @@ function tabLoaded() {
|
|||
ok(find("iter2: Iterator", false),
|
||||
"iter2 is correctly displayed in the Property Panel");
|
||||
|
||||
/*
|
||||
* - disabled, see bug 632347, c#9
|
||||
* ok(find("parent: Window", true),
|
||||
* "window.parent is correctly displayed in the Property Panel");
|
||||
*/
|
||||
|
||||
panel.destroy();
|
||||
|
||||
finishTest();
|
||||
executeSoon(finishTest);
|
||||
}
|
||||
|
|
|
@ -8,13 +8,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
|
|||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
"nsIClipboardHelper");
|
||||
|
||||
function tabLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
|
||||
openConsole();
|
||||
|
||||
let hudId = HUDService.getHudIdByWindow(content);
|
||||
let HUD = HUDService.hudReferences[hudId];
|
||||
function consoleOpened(HUD) {
|
||||
let jsterm = HUD.jsterm;
|
||||
let stringToCopy = "foobazbarBug642615";
|
||||
|
||||
|
@ -24,41 +18,11 @@ function tabLoad(aEvent) {
|
|||
|
||||
jsterm.setInputValue("doc");
|
||||
|
||||
let completionValue;
|
||||
|
||||
// wait for key "u"
|
||||
jsterm.inputNode.addEventListener("keyup", function() {
|
||||
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
|
||||
|
||||
let completionValue = jsterm.completeNode.value;
|
||||
ok(completionValue, "we have a completeNode.value");
|
||||
|
||||
// wait for paste
|
||||
jsterm.inputNode.addEventListener("input", function() {
|
||||
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
|
||||
|
||||
ok(!jsterm.completeNode.value, "no completeNode.value after clipboard paste");
|
||||
|
||||
// wait for undo
|
||||
jsterm.inputNode.addEventListener("input", function() {
|
||||
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
|
||||
|
||||
is(jsterm.completeNode.value, completionValue,
|
||||
"same completeNode.value after undo");
|
||||
|
||||
// wait for paste (via keyboard event)
|
||||
jsterm.inputNode.addEventListener("keyup", function() {
|
||||
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
|
||||
|
||||
ok(!jsterm.completeNode.value,
|
||||
"no completeNode.value after clipboard paste (via keyboard event)");
|
||||
|
||||
executeSoon(finishTest);
|
||||
}, false);
|
||||
|
||||
EventUtils.synthesizeKey("v", {accelKey: true});
|
||||
}, false);
|
||||
|
||||
goDoCommand("cmd_undo");
|
||||
}, false);
|
||||
function onCompletionValue() {
|
||||
completionValue = jsterm.completeNode.value;
|
||||
|
||||
// Arguments: expected, setup, success, failure.
|
||||
waitForClipboard(
|
||||
|
@ -66,17 +30,73 @@ function tabLoad(aEvent) {
|
|||
function() {
|
||||
clipboardHelper.copyString(stringToCopy);
|
||||
},
|
||||
function() {
|
||||
onClipboardCopy,
|
||||
finishTest);
|
||||
}
|
||||
|
||||
function onClipboardCopy() {
|
||||
updateEditUIVisibility();
|
||||
goDoCommand("cmd_paste");
|
||||
|
||||
waitForSuccess(waitForPaste);
|
||||
}
|
||||
|
||||
let waitForPaste = {
|
||||
name: "no completion value after paste",
|
||||
validatorFn: function()
|
||||
{
|
||||
return !jsterm.completeNode.value;
|
||||
},
|
||||
finish);
|
||||
}, false);
|
||||
successFn: onClipboardPaste,
|
||||
failureFn: finishTest,
|
||||
};
|
||||
|
||||
function onClipboardPaste() {
|
||||
goDoCommand("cmd_undo");
|
||||
waitForSuccess({
|
||||
name: "completion value for 'docu' after undo",
|
||||
validatorFn: function()
|
||||
{
|
||||
return !!jsterm.completeNode.value;
|
||||
},
|
||||
successFn: onCompletionValueAfterUndo,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function onCompletionValueAfterUndo() {
|
||||
is(jsterm.completeNode.value, completionValue,
|
||||
"same completeNode.value after undo");
|
||||
|
||||
EventUtils.synthesizeKey("v", {accelKey: true});
|
||||
waitForSuccess({
|
||||
name: "no completion after ctrl-v (paste)",
|
||||
validatorFn: function()
|
||||
{
|
||||
return !jsterm.completeNode.value;
|
||||
},
|
||||
successFn: finishTest,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
EventUtils.synthesizeKey("u", {});
|
||||
|
||||
waitForSuccess({
|
||||
name: "completion value for 'docu'",
|
||||
validatorFn: function()
|
||||
{
|
||||
return !!jsterm.completeNode.value;
|
||||
},
|
||||
successFn: onCompletionValue,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", tabLoad, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
|
|
@ -6,38 +6,36 @@
|
|||
|
||||
// Tests that document.body autocompletes in the web console.
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/PropertyPanel.jsm", tempScope);
|
||||
let PropertyPanel = tempScope.PropertyPanel;
|
||||
let PropertyTreeView = tempScope.PropertyTreeView;
|
||||
let namesAndValuesOf = tempScope.namesAndValuesOf;
|
||||
let isNonNativeGetter = tempScope.isNonNativeGetter;
|
||||
|
||||
function test() {
|
||||
addTab("data:text/html;charset=utf-8,Web Console autocompletion bug in document.body");
|
||||
browser.addEventListener("load", onLoad, true);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
||||
var gHUD;
|
||||
|
||||
function onLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
openConsole();
|
||||
let hudId = HUDService.getHudIdByWindow(content);
|
||||
gHUD = HUDService.hudReferences[hudId];
|
||||
function consoleOpened(aHud) {
|
||||
gHUD = aHud;
|
||||
let jsterm = gHUD.jsterm;
|
||||
let popup = jsterm.autocompletePopup;
|
||||
let completeNode = jsterm.completeNode;
|
||||
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
|
||||
let WCU = tmp.WebConsoleUtils;
|
||||
tmp = null;
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
|
||||
popup._panel.addEventListener("popupshown", function() {
|
||||
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||
popup._panel.addEventListener("popupshown", function onShown() {
|
||||
popup._panel.removeEventListener("popupshown", onShown, false);
|
||||
|
||||
ok(popup.isOpen, "popup is open");
|
||||
|
||||
let props = namesAndValuesOf(content.wrappedJSObject.document.body).length;
|
||||
is(popup.itemCount, props, "popup.itemCount is correct");
|
||||
let props = WCU.namesAndValuesOf(content.wrappedJSObject.document.body);
|
||||
is(popup.itemCount, props.length, "popup.itemCount is correct");
|
||||
|
||||
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
|
||||
|
||||
|
@ -55,32 +53,80 @@ function autocompletePopupHidden()
|
|||
let completeNode = jsterm.completeNode;
|
||||
let inputNode = jsterm.inputNode;
|
||||
|
||||
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
|
||||
popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
let inputStr = "document.b";
|
||||
jsterm.setInputValue(inputStr);
|
||||
EventUtils.synthesizeKey("o", {});
|
||||
let testStr = inputStr.replace(/./g, " ") + " ";
|
||||
is(completeNode.value, testStr + "dy", "completeNode is empty");
|
||||
jsterm.setInputValue("");
|
||||
|
||||
// Check the property panel as well. It's a bit gross to parse the properties
|
||||
// out of the treeView cell text, but nsITreeView doesn't give us a good
|
||||
// structured way to get at the data. :-(
|
||||
let propPanel = jsterm.openPropertyPanel("Test", content.document);
|
||||
waitForSuccess({
|
||||
name: "autocomplete shows document.body",
|
||||
validatorFn: function()
|
||||
{
|
||||
return completeNode.value == testStr + "dy";
|
||||
},
|
||||
successFn: testPropertyPanel,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function testPropertyPanel()
|
||||
{
|
||||
let jsterm = gHUD.jsterm;
|
||||
jsterm.clearOutput();
|
||||
jsterm.setInputValue("document");
|
||||
jsterm.execute();
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm document object output",
|
||||
validatorFn: function()
|
||||
{
|
||||
return gHUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
document.addEventListener("popupshown", function onShown(aEvent) {
|
||||
document.removeEventListener("popupshown", onShown, false);
|
||||
executeSoon(propertyPanelShown.bind(null, aEvent.target));
|
||||
}, false);
|
||||
|
||||
let node = gHUD.outputNode.querySelector(".webconsole-msg-output");
|
||||
EventUtils.synthesizeMouse(node, 2, 2, {});
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function propertyPanelShown(aPanel)
|
||||
{
|
||||
let tree = aPanel.querySelector("tree");
|
||||
let view = tree.view;
|
||||
let col = tree.columns[0];
|
||||
ok(view.rowCount, "Property Panel rowCount");
|
||||
|
||||
let foundBody = false;
|
||||
let propPanelProps = [];
|
||||
for (let idx = 0; idx < propPanel.treeView.rowCount; ++idx)
|
||||
propPanelProps.push(propPanel.treeView.getCellText(idx, null).split(':')[0]);
|
||||
for (let idx = 0; idx < view.rowCount; ++idx) {
|
||||
let text = view.getCellText(idx, col);
|
||||
if (text == "body: HTMLBodyElement" || text == "body: Object")
|
||||
foundBody = true;
|
||||
propPanelProps.push(text.split(":")[0]);
|
||||
}
|
||||
|
||||
// NB: We pull the properties off the prototype, rather than off object itself,
|
||||
// so that expandos like |constructor|, which the propPanel can't see, are not
|
||||
// included.
|
||||
for (let prop in Object.getPrototypeOf(content.document))
|
||||
for (let prop in Object.getPrototypeOf(content.document)) {
|
||||
if (prop == "inputEncoding") {
|
||||
continue;
|
||||
}
|
||||
ok(propPanelProps.indexOf(prop) != -1, "Property |" + prop + "| should be reflected in propertyPanel");
|
||||
}
|
||||
|
||||
ok(foundBody, "found document.body");
|
||||
|
||||
let treeRows = propPanel.treeView._rows;
|
||||
is (treeRows[30].display, "body: Object", "found document.body");
|
||||
propPanel.destroy();
|
||||
executeSoon(finishTest);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
|
||||
// Tests that the $0 console helper works as intended.
|
||||
|
||||
let doc;
|
||||
let h1;
|
||||
|
||||
function createDocument()
|
||||
{
|
||||
let doc = content.document;
|
||||
let div = doc.createElement("div");
|
||||
let h1 = doc.createElement("h1");
|
||||
let p1 = doc.createElement("p");
|
||||
|
@ -44,7 +42,7 @@ function createDocument()
|
|||
|
||||
function setupHighlighterTests()
|
||||
{
|
||||
h1 = doc.querySelectorAll("h1")[0];
|
||||
let h1 = content.document.querySelector("h1");
|
||||
ok(h1, "we have the header node");
|
||||
Services.obs.addObserver(runSelectionTests,
|
||||
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
|
||||
|
@ -58,6 +56,7 @@ function runSelectionTests()
|
|||
|
||||
executeSoon(function() {
|
||||
InspectorUI.highlighter.addListener("nodeselected", performTestComparisons);
|
||||
let h1 = content.document.querySelector("h1");
|
||||
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
|
||||
});
|
||||
}
|
||||
|
@ -67,6 +66,8 @@ function performTestComparisons()
|
|||
InspectorUI.highlighter.removeListener("nodeselected", performTestComparisons);
|
||||
|
||||
InspectorUI.stopInspecting();
|
||||
|
||||
let h1 = content.document.querySelector("h1");
|
||||
is(InspectorUI.highlighter.node, h1, "node selected");
|
||||
is(InspectorUI.selection, h1, "selection matches node");
|
||||
|
||||
|
@ -80,16 +81,44 @@ function performWebConsoleTests(hud)
|
|||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("$0");
|
||||
findLogEntry("[object HTMLHeadingElement");
|
||||
|
||||
waitForSuccess({
|
||||
name: "$0 output",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let node = outputNode.querySelector(".webconsole-msg-output");
|
||||
isnot(node.textContent.indexOf("[object HTMLHeadingElement"), -1,
|
||||
"correct output for $0");
|
||||
|
||||
jsterm.clearOutput();
|
||||
let msg = "foo";
|
||||
jsterm.execute("$0.textContent = '" + msg + "'");
|
||||
findLogEntry(msg);
|
||||
is(InspectorUI.selection.textContent, msg, "node successfully updated");
|
||||
jsterm.execute("$0.textContent = 'bug653531'");
|
||||
waitForSuccess(waitForNodeUpdate);
|
||||
},
|
||||
failureFn: finishUp,
|
||||
});
|
||||
|
||||
let waitForNodeUpdate = {
|
||||
name: "$0.textContent update",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let node = outputNode.querySelector(".webconsole-msg-output");
|
||||
isnot(node.textContent.indexOf("bug653531"), -1,
|
||||
"correct output for $0.textContent");
|
||||
is(InspectorUI.selection.textContent, "bug653531",
|
||||
"node successfully updated");
|
||||
|
||||
doc = h1 = null;
|
||||
executeSoon(finishUp);
|
||||
},
|
||||
failureFn: finishUp,
|
||||
};
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
|
@ -103,7 +132,6 @@ function test()
|
|||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
doc = content.document;
|
||||
waitForFocus(createDocument, content);
|
||||
}, true);
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
// Tests that code completion works properly.
|
||||
|
||||
function test() {
|
||||
addTab(getBrowserURL());
|
||||
browser.addEventListener("DOMContentLoaded", function onLoad() {
|
||||
browser.removeEventListener("DOMContentLoaded", onLoad, true);
|
||||
openConsole();
|
||||
testChrome(HUDService.getHudByWindow(content));
|
||||
addTab("about:addons");
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testChrome);
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -28,10 +27,9 @@ function testChrome(hud) {
|
|||
// Test typing 'docu'.
|
||||
input.value = "docu";
|
||||
input.setSelectionRange(4, 4);
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, function() {
|
||||
is(jsterm.completeNode.value, " ment", "'docu' completion");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
executeSoon(finishTest);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,32 +5,46 @@
|
|||
|
||||
// Tests that code completion works properly.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
|
||||
const TEST_URI = "data:text/html;charset=utf8,<p>test code completion";
|
||||
|
||||
let testDriver;
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("DOMContentLoaded", testCompletion, false);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, function(hud) {
|
||||
testDriver = testCompletion(hud);
|
||||
testDriver.next();
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
function testCompletion() {
|
||||
browser.removeEventListener("DOMContentLoaded", testCompletion, false);
|
||||
function testNext() {
|
||||
executeSoon(function() {
|
||||
testDriver.next();
|
||||
});
|
||||
}
|
||||
|
||||
openConsole();
|
||||
|
||||
var jsterm = HUDService.getHudByWindow(content).jsterm;
|
||||
var input = jsterm.inputNode;
|
||||
function testCompletion(hud) {
|
||||
let jsterm = hud.jsterm;
|
||||
let input = jsterm.inputNode;
|
||||
|
||||
// Test typing 'docu'.
|
||||
input.value = "docu";
|
||||
input.setSelectionRange(4, 4);
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
|
||||
is(input.value, "docu", "'docu' completion");
|
||||
is(jsterm.completeNode.value, " ment", "'docu' completion");
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
|
||||
yield;
|
||||
|
||||
is(input.value, "docu", "'docu' completion (input.value)");
|
||||
is(jsterm.completeNode.value, " ment", "'docu' completion (completeNode)");
|
||||
|
||||
// Test typing 'docu' and press tab.
|
||||
input.value = "docu";
|
||||
input.setSelectionRange(4, 4);
|
||||
jsterm.complete(jsterm.COMPLETE_FORWARD);
|
||||
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
|
||||
yield;
|
||||
|
||||
is(input.value, "document", "'docu' tab completion");
|
||||
is(input.selectionStart, 8, "start selection is alright");
|
||||
is(input.selectionEnd, 8, "end selection is alright");
|
||||
|
@ -39,35 +53,45 @@ function testCompletion() {
|
|||
// Test typing 'document.getElem'.
|
||||
input.value = "document.getElem";
|
||||
input.setSelectionRange(16, 16);
|
||||
jsterm.complete(jsterm.COMPLETE_FORWARD);
|
||||
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
|
||||
yield;
|
||||
|
||||
is(input.value, "document.getElem", "'document.getElem' completion");
|
||||
is(jsterm.completeNode.value, " entById", "'document.getElem' completion");
|
||||
|
||||
// Test pressing tab another time.
|
||||
jsterm.complete(jsterm.COMPLETE_FORWARD);
|
||||
jsterm.complete(jsterm.COMPLETE_FORWARD, testNext);
|
||||
yield;
|
||||
|
||||
is(input.value, "document.getElem", "'document.getElem' completion");
|
||||
is(jsterm.completeNode.value, " entsByClassName", "'document.getElem' another tab completion");
|
||||
|
||||
// Test pressing shift_tab.
|
||||
jsterm.complete(jsterm.COMPLETE_BACKWARD);
|
||||
jsterm.complete(jsterm.COMPLETE_BACKWARD, testNext);
|
||||
yield;
|
||||
|
||||
is(input.value, "document.getElem", "'document.getElem' untab completion");
|
||||
is(jsterm.completeNode.value, " entById", "'document.getElem' completion");
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
|
||||
|
||||
input.value = "docu";
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
|
||||
yield;
|
||||
|
||||
is(jsterm.completeNode.value, " ment", "'docu' completion");
|
||||
jsterm.execute();
|
||||
is(jsterm.completeNode.value, "", "clear completion on execute()");
|
||||
|
||||
// Test multi-line completion works
|
||||
input.value = "console.log('one');\nconsol";
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
|
||||
yield;
|
||||
|
||||
is(jsterm.completeNode.value, " \n e", "multi-line completion");
|
||||
|
||||
jsterm = input = null;
|
||||
finishTest();
|
||||
testDriver = jsterm = input = null;
|
||||
executeSoon(finishTest);
|
||||
yield;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,17 +14,15 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
|
|||
function test()
|
||||
{
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function() {
|
||||
browser.removeEventListener("load", arguments.callee, true);
|
||||
testOpenWebConsole();
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testOpenWebConsole);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function testOpenWebConsole()
|
||||
function testOpenWebConsole(aHud)
|
||||
{
|
||||
openConsole();
|
||||
|
||||
hud = HUDService.getHudByWindow(content);
|
||||
hud = aHud;
|
||||
ok(hud, "WebConsole was opened");
|
||||
|
||||
testOwnConsole();
|
||||
|
@ -42,8 +40,8 @@ function testOwnConsole()
|
|||
// overwritten by the WebConsole's console.
|
||||
testConsoleOnPage(console);
|
||||
|
||||
// Check that the console object is set on the jsterm object although there
|
||||
// Check that the console object is set on the HUD object although there
|
||||
// is no console object added to the page.
|
||||
ok(hud.jsterm.console, "JSTerm console is defined");
|
||||
ok(hud.console, "HUD console is defined");
|
||||
finishTest();
|
||||
}
|
||||
|
|
|
@ -19,17 +19,27 @@ function testExecutionScope(hud) {
|
|||
let jsterm = hud.jsterm;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("location;");
|
||||
jsterm.execute("window.location;");
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm execution output (two nodes)",
|
||||
validatorFn: function()
|
||||
{
|
||||
return jsterm.outputNode.querySelectorAll(".hud-msg-node").length == 2;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
|
||||
is(nodes.length, 2, "Two children in output");
|
||||
|
||||
is(/location;/.test(nodes[0].textContent), true,
|
||||
"'location;' written to output");
|
||||
is(/window.location;/.test(nodes[0].textContent), true,
|
||||
"'window.location;' written to output");
|
||||
|
||||
ok(nodes[0].textContent.indexOf(TEST_URI),
|
||||
isnot(nodes[1].textContent.indexOf(TEST_URI), -1,
|
||||
"command was executed in the window scope");
|
||||
|
||||
executeSoon(finishTest);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,22 +7,29 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
|
|||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("DOMContentLoaded", testForOf, false);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testForOf);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function testForOf() {
|
||||
browser.removeEventListener("DOMContentLoaded", testForOf, false);
|
||||
|
||||
openConsole();
|
||||
var hud = HUDService.getHudByWindow(content);
|
||||
function testForOf(hud) {
|
||||
var jsterm = hud.jsterm;
|
||||
jsterm.execute("{ [x.tagName for (x of document.body.childNodes) if (x.nodeType === 1)].join(' '); }");
|
||||
|
||||
waitForSuccess({
|
||||
name: "jsterm output displayed",
|
||||
validatorFn: function()
|
||||
{
|
||||
return hud.outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let node = hud.outputNode.querySelector(".webconsole-msg-output");
|
||||
ok(/H1 DIV H2 P/.test(node.textContent),
|
||||
"for-of loop should find all top-level nodes");
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
|
||||
finishTest();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,38 +10,38 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
|
|||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("DOMContentLoaded", testJSInputAndOutputStyling,
|
||||
false);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testJSInputAndOutputStyling);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function testJSInputAndOutputStyling() {
|
||||
browser.removeEventListener("DOMContentLoaded",
|
||||
testJSInputAndOutputStyling, false);
|
||||
|
||||
openConsole();
|
||||
|
||||
let jsterm = HUDService.getHudByWindow(content).jsterm;
|
||||
function testJSInputAndOutputStyling(hud) {
|
||||
let jsterm = hud.jsterm;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("2 + 2");
|
||||
|
||||
let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
|
||||
let jsInputNode = nodes[0];
|
||||
let jsInputNode = jsterm.outputNode.querySelector(".hud-msg-node");
|
||||
isnot(jsInputNode.textContent.indexOf("2 + 2"), -1,
|
||||
"JS input node contains '2 + 2'");
|
||||
ok(jsInputNode.classList.contains("webconsole-msg-input"),
|
||||
"JS input node is of the CSS class 'webconsole-msg-input'");
|
||||
|
||||
let jsOutputNodes = jsterm.outputNode.
|
||||
querySelectorAll(".webconsole-msg-output");
|
||||
isnot(jsOutputNodes[0].textContent.indexOf("4"), -1,
|
||||
waitForSuccess({
|
||||
name: "jsterm output is displayed",
|
||||
validatorFn: function()
|
||||
{
|
||||
return jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let node = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
isnot(node.textContent.indexOf("4"), -1,
|
||||
"JS output node contains '4'");
|
||||
ok(jsOutputNodes[0].classList.contains("webconsole-msg-output"),
|
||||
"JS output node is of the CSS class 'webconsole-msg-output'");
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
|
||||
|
||||
finishTest();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,21 +5,46 @@
|
|||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
|
||||
|
||||
let jsterm;
|
||||
let jsterm, testDriver;
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testJSTerm);
|
||||
openConsole(null, function(hud) {
|
||||
testDriver = testJSTerm(hud);
|
||||
testDriver.next();
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
testDriver.next();
|
||||
}
|
||||
|
||||
function checkResult(msg, desc, lines) {
|
||||
waitForSuccess({
|
||||
name: "correct number of results shown for " + desc,
|
||||
validatorFn: function()
|
||||
{
|
||||
let nodes = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
|
||||
return nodes.length == lines;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
|
||||
is(labels.length, lines, "correct number of results shown for " + desc);
|
||||
is(labels[lines-1].textContent.trim(), msg, "correct message shown for " +
|
||||
desc);
|
||||
if (typeof msg == "string") {
|
||||
is(labels[lines-1].textContent.trim(), msg,
|
||||
"correct message shown for " + desc);
|
||||
}
|
||||
else if (typeof msg == "function") {
|
||||
ok(msg(labels), "correct message shown for " + desc);
|
||||
}
|
||||
|
||||
nextTest();
|
||||
},
|
||||
failureFn: nextTest,
|
||||
});
|
||||
}
|
||||
|
||||
function testJSTerm(hud)
|
||||
|
@ -29,34 +54,52 @@ function testJSTerm(hud)
|
|||
jsterm.clearOutput();
|
||||
jsterm.execute("'id=' + $('header').getAttribute('id')");
|
||||
checkResult('"id=header"', "$() worked", 1);
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("headerQuery = $$('h1')");
|
||||
jsterm.execute("'length=' + headerQuery.length");
|
||||
checkResult('"length=1"', "$$() worked", 2);
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("xpathQuery = $x('.//*', document.body);");
|
||||
jsterm.execute("'headerFound=' + (xpathQuery[0] == headerQuery[0])");
|
||||
checkResult('"headerFound=true"', "$x() worked", 2);
|
||||
yield;
|
||||
|
||||
// no jsterm.clearOutput() here as we clear the output using the clear() fn.
|
||||
jsterm.execute("clear()");
|
||||
let group = jsterm.outputNode.querySelector(".hud-group");
|
||||
ok(!group, "clear() worked");
|
||||
|
||||
waitForSuccess({
|
||||
name: "clear() worked",
|
||||
validatorFn: function()
|
||||
{
|
||||
return jsterm.outputNode.childNodes.length == 0;
|
||||
},
|
||||
successFn: nextTest,
|
||||
failureFn: nextTest,
|
||||
});
|
||||
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("'keysResult=' + (keys({b:1})[0] == 'b')");
|
||||
checkResult('"keysResult=true"', "keys() worked", 1);
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("'valuesResult=' + (values({b:1})[0] == 1)");
|
||||
checkResult('"valuesResult=true"', "values() worked", 1);
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
|
||||
let tabs = gBrowser.tabs.length;
|
||||
|
||||
jsterm.execute("help()");
|
||||
let output = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
ok(!group, "help() worked");
|
||||
ok(!output, "help() worked");
|
||||
|
||||
jsterm.execute("help");
|
||||
output = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
|
@ -66,57 +109,84 @@ function testJSTerm(hud)
|
|||
output = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
ok(!output, "? worked");
|
||||
|
||||
let foundTab = null;
|
||||
waitForSuccess({
|
||||
name: "help tab opened",
|
||||
validatorFn: function()
|
||||
{
|
||||
let newTabOpen = gBrowser.tabs.length == tabs + 1;
|
||||
if (!newTabOpen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foundTab = gBrowser.tabs[tabs];
|
||||
return true;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
gBrowser.removeTab(foundTab);
|
||||
nextTest();
|
||||
},
|
||||
failureFn: nextTest,
|
||||
});
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("pprint({b:2, a:1})");
|
||||
// Doesn't conform to checkResult format
|
||||
let label = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
is(label.textContent.trim(), "a: 1\n b: 2", "pprint() worked");
|
||||
checkResult("a: 1\n b: 2", "pprint()", 1);
|
||||
yield;
|
||||
|
||||
// check instanceof correctness, bug 599940
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("[] instanceof Array");
|
||||
checkResult("true", "[] instanceof Array == true", 1);
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("({}) instanceof Object");
|
||||
checkResult("true", "({}) instanceof Object == true", 1);
|
||||
yield;
|
||||
|
||||
// check for occurrences of Object XRayWrapper, bug 604430
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("document");
|
||||
let label = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
is(label.textContent.trim().search(/\[object XrayWrapper/), -1,
|
||||
"check for non-existence of [object XrayWrapper ");
|
||||
checkResult(function(nodes) {
|
||||
return nodes[0].textContent.search(/\[object xraywrapper/i) == -1;
|
||||
}, "document - no XrayWrapper", 1);
|
||||
yield;
|
||||
|
||||
// check that pprint(window) and keys(window) don't throw, bug 608358
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("pprint(window)");
|
||||
let labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
|
||||
is(labels.length, 1, "one line of output for pprint(window)");
|
||||
checkResult(null, "pprint(window)", 1);
|
||||
yield;
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("keys(window)");
|
||||
labels = jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
|
||||
is(labels.length, 1, "one line of output for keys(window)");
|
||||
checkResult(null, "keys(window)", 1);
|
||||
yield;
|
||||
|
||||
// bug 614561
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("pprint('hi')");
|
||||
// Doesn't conform to checkResult format, bug 614561
|
||||
let label = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
is(label.textContent.trim(), '0: "h"\n 1: "i"', 'pprint("hi") worked');
|
||||
checkResult('0: "h"\n 1: "i"', "pprint('hi')", 1);
|
||||
yield;
|
||||
|
||||
// check that pprint(function) shows function source, bug 618344
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("pprint(print)");
|
||||
label = jsterm.outputNode.querySelector(".webconsole-msg-output");
|
||||
isnot(label.textContent.indexOf("SEVERITY_LOG"), -1,
|
||||
"pprint(function) shows function source");
|
||||
checkResult(function(nodes) {
|
||||
return nodes[0].textContent.indexOf("aJSTerm.") > -1;
|
||||
}, "pprint(function) shows source", 1);
|
||||
yield;
|
||||
|
||||
// check that an evaluated null produces "null", bug 650780
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("null");
|
||||
checkResult("null", "null is null", 1);
|
||||
yield;
|
||||
|
||||
jsterm = null;
|
||||
jsterm = testDriver = null;
|
||||
executeSoon(finishTest);
|
||||
yield;
|
||||
}
|
||||
|
|
|
@ -23,17 +23,40 @@ function testNullAndUndefinedOutput(hud) {
|
|||
jsterm.clearOutput();
|
||||
jsterm.execute("null;");
|
||||
|
||||
waitForSuccess({
|
||||
name: "null displayed",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelectorAll(".hud-msg-node").length == 2;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let nodes = outputNode.querySelectorAll(".hud-msg-node");
|
||||
is(nodes.length, 2, "2 nodes in output");
|
||||
ok(nodes[1].textContent.indexOf("null") > -1, "'null' printed to output");
|
||||
isnot(nodes[1].textContent.indexOf("null"), -1,
|
||||
"'null' printed to output");
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("undefined;");
|
||||
waitForSuccess(waitForUndefined);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
|
||||
nodes = outputNode.querySelectorAll(".hud-msg-node");
|
||||
is(nodes.length, 2, "2 nodes in output");
|
||||
ok(nodes[1].textContent.indexOf("undefined") > -1, "'undefined' printed to output");
|
||||
let waitForUndefined = {
|
||||
name: "undefined displayed",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelectorAll(".hud-msg-node").length == 2;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let nodes = outputNode.querySelectorAll(".hud-msg-node");
|
||||
isnot(nodes[1].textContent.indexOf("undefined"), -1,
|
||||
"'undefined' printed to output");
|
||||
|
||||
executeSoon(finishTest);
|
||||
finishTest();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -36,8 +36,7 @@ function testOutputOrder(hud) {
|
|||
/console\.log\('foo', 'bar'\);/.test(nodes[0].textContent);
|
||||
let outputSecond = /foo bar/.test(nodes[2].textContent);
|
||||
ok(executedStringFirst && outputSecond, "executed string comes first");
|
||||
jsterm.clearOutput();
|
||||
jsterm.history.splice(0, jsterm.history.length); // workaround for bug 592552
|
||||
|
||||
finishTest();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
|
|
|
@ -6,32 +6,37 @@
|
|||
// Tests the functionality of the "property panel", which allows JavaScript
|
||||
// objects and DOM nodes to be inspected.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
|
||||
const TEST_URI = "data:text/html;charset=utf8,<p>property panel test";
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("DOMContentLoaded", testPropertyPanel, false);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testPropertyPanel);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function testPropertyPanel() {
|
||||
browser.removeEventListener("DOMContentLoaded", testPropertyPanel, false);
|
||||
function testPropertyPanel(hud) {
|
||||
let jsterm = hud.jsterm;
|
||||
|
||||
openConsole();
|
||||
|
||||
var jsterm = HUDService.getHudByWindow(content).jsterm;
|
||||
|
||||
let propPanel = jsterm.openPropertyPanel("Test", [
|
||||
let propPanel = jsterm.openPropertyPanel({
|
||||
data: {
|
||||
object: [
|
||||
1,
|
||||
/abc/,
|
||||
null,
|
||||
undefined,
|
||||
function test() {},
|
||||
{}
|
||||
]);
|
||||
]
|
||||
}
|
||||
});
|
||||
is (propPanel.treeView.rowCount, 6, "six elements shown in propertyPanel");
|
||||
propPanel.destroy();
|
||||
|
||||
propPanel = jsterm.openPropertyPanel("Test2", {
|
||||
propPanel = jsterm.openPropertyPanel({
|
||||
data: {
|
||||
object: {
|
||||
"0.02": 0,
|
||||
"0.01": 1,
|
||||
"02": 2,
|
||||
|
@ -41,21 +46,23 @@ function testPropertyPanel() {
|
|||
"1.1": 6,
|
||||
"foo": 7,
|
||||
"bar": 8
|
||||
}
|
||||
}
|
||||
});
|
||||
is (propPanel.treeView.rowCount, 9, "nine elements shown in propertyPanel");
|
||||
|
||||
let treeRows = propPanel.treeView._rows;
|
||||
is (treeRows[0].display, "0.01: 1", "1. element is okay");
|
||||
is (treeRows[1].display, "0.02: 0", "2. element is okay");
|
||||
is (treeRows[2].display, "1: 3", "3. element is okay");
|
||||
is (treeRows[3].display, "1.1: 6", "4. element is okay");
|
||||
is (treeRows[4].display, "1.2: 5", "5. element is okay");
|
||||
is (treeRows[5].display, "02: 2", "6. element is okay");
|
||||
is (treeRows[6].display, "11: 4", "7. element is okay");
|
||||
is (treeRows[7].display, "bar: 8", "8. element is okay");
|
||||
is (treeRows[8].display, "foo: 7", "9. element is okay");
|
||||
let view = propPanel.treeView;
|
||||
is (view.getCellText(0), "0.01: 1", "1. element is okay");
|
||||
is (view.getCellText(1), "0.02: 0", "2. element is okay");
|
||||
is (view.getCellText(2), "1: 3", "3. element is okay");
|
||||
is (view.getCellText(3), "1.1: 6", "4. element is okay");
|
||||
is (view.getCellText(4), "1.2: 5", "5. element is okay");
|
||||
is (view.getCellText(5), "02: 2", "6. element is okay");
|
||||
is (view.getCellText(6), "11: 4", "7. element is okay");
|
||||
is (view.getCellText(7), "bar: 8", "8. element is okay");
|
||||
is (view.getCellText(8), "foo: 7", "9. element is okay");
|
||||
propPanel.destroy();
|
||||
|
||||
finishTest();
|
||||
executeSoon(finishTest);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,43 +6,37 @@
|
|||
// Tests the property provider, which is part of the code completion
|
||||
// infrastructure.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
|
||||
const TEST_URI = "data:text/html;charset=utf8,<p>test the JS property provider";
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("DOMContentLoaded", testPropertyProvider, false);
|
||||
browser.addEventListener("load", testPropertyProvider, true);
|
||||
}
|
||||
|
||||
function testPropertyProvider() {
|
||||
browser.removeEventListener("DOMContentLoaded", testPropertyProvider,
|
||||
false);
|
||||
browser.removeEventListener("load", testPropertyProvider, true);
|
||||
|
||||
openConsole();
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/WebConsoleUtils.jsm", tmp);
|
||||
let JSPropertyProvider = tmp.JSPropertyProvider;
|
||||
tmp = null;
|
||||
|
||||
var HUD = HUDService.getHudByWindow(content);
|
||||
var jsterm = HUD.jsterm;
|
||||
var context = jsterm.sandbox.window;
|
||||
var completion;
|
||||
|
||||
// Test if the propertyProvider can be accessed from the jsterm object.
|
||||
ok (jsterm.propertyProvider !== undefined, "JSPropertyProvider is defined");
|
||||
|
||||
completion = jsterm.propertyProvider(context, "thisIsNotDefined");
|
||||
let completion = JSPropertyProvider(content, "thisIsNotDefined");
|
||||
is (completion.matches.length, 0, "no match for 'thisIsNotDefined");
|
||||
|
||||
// This is a case the PropertyProvider can't handle. Should return null.
|
||||
completion = jsterm.propertyProvider(context, "window[1].acb");
|
||||
completion = JSPropertyProvider(content, "window[1].acb");
|
||||
is (completion, null, "no match for 'window[1].acb");
|
||||
|
||||
// A very advanced completion case.
|
||||
var strComplete =
|
||||
'function a() { }document;document.getElementById(window.locatio';
|
||||
completion = jsterm.propertyProvider(context, strComplete);
|
||||
completion = JSPropertyProvider(content, strComplete);
|
||||
ok(completion.matches.length == 2, "two matches found");
|
||||
ok(completion.matchProp == "locatio", "matching part is 'test'");
|
||||
ok(completion.matches[0] == "location", "the first match is 'location'");
|
||||
ok(completion.matches[1] == "locationbar", "the second match is 'locationbar'");
|
||||
context = completion = null;
|
||||
|
||||
finishTest();
|
||||
}
|
||||
|
||||
|
|
|
@ -191,3 +191,9 @@ timeEnd=%1$S: %2$Sms
|
|||
Autocomplete.blank= <- no result
|
||||
|
||||
maxTimersExceeded=The maximum allowed number of timers in this page was exceeded.
|
||||
|
||||
# LOCALIZATION NOTE (JSTerm.updateNotInspectable):
|
||||
# This string is used when the user inspects an evaluation result in the Web
|
||||
# Console and tries the Update button, but the new result no longer returns an
|
||||
# object that can be inspected.
|
||||
JSTerm.updateNotInspectable=After your input has been re-evaluated the result is no longer inspectable.
|
||||
|
|
Загрузка…
Ссылка в новой задаче