Bug 673148 - (async-webconsole) Part 3 - Make JS evaluation and object inspection async; r=rcampbell

This commit is contained in:
Mihai Sucan 2012-05-25 13:28:47 +03:00
Родитель 94ec7ae1dc
Коммит f37529970d
30 изменённых файлов: 2043 добавлений и 2118 удалений

Просмотреть файл

@ -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);
}
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
}
else {
item.isOpened = true;
let levelUpdate = true;
let callback = function _onRemoteResponse(aResponse) {
this._treeBox.beginUpdateBatch();
item.isOpened = true;
var toInsert = this.getChildItems(item);
item.children = toInsert;
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(toInsert));
if (levelUpdate) {
this._updateRemoteObject(aResponse.object, item.level + 1);
item.children = aResponse.object;
}
this._treeBox.rowCountChanged(idx + 1, toInsert.length);
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});
}
}
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
},
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,8 +529,13 @@ var WebConsoleUtils = {
presentable = {type: TYPES.GETTER, display: "Getter"};
}
else {
value = aObject[propName];
presentable = this.presentableValueFor(value);
try {
value = aObject[propName];
presentable = this.presentableValueFor(value);
}
catch (ex) {
continue;
}
}
let pair = {};
@ -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");
waitForSuccess({
name: "jsterm output message",
validatorFn: function()
{
return outputNode.querySelector(".webconsole-msg-output .hud-clickable");
},
successFn: function()
{
document.addEventListener("popupshown", propertyPanelShown, false);
document.addEventListener("popupshown", properyPanelShown, false);
// Send the mousedown and click events such that the property panel opens.
EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
EventUtils.sendMouseEvent({type: "click"}, outputItem);
// 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();
content.location.reload();
browser.addEventListener("load", tabLoaded, true);
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() {
}
});
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
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.
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
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);
function test$(HUD) {
HUD.jsterm.clearOutput();
openConsole(null, function(HUD) {
HUD.jsterm.clearOutput();
HUD.jsterm.setInputValue("$(document.body)");
HUD.jsterm.execute();
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 $()");
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),
});
}
HUD.jsterm.clearOutput();
function test$$(HUD) {
HUD.jsterm.clearOutput();
HUD.jsterm.setInputValue("$$(document)");
HUD.jsterm.execute();
HUD.jsterm.setInputValue("$$(document)");
HUD.jsterm.execute();
outputItem = HUD.outputNode.
querySelector(".webconsole-msg-output:last-child");
ok(outputItem.textContent.indexOf("621644") > -1,
"jsterm output is correct for $$()");
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);
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() {
updateEditUIVisibility();
goDoCommand("cmd_paste");
onClipboardCopy,
finishTest);
}
function onClipboardCopy() {
updateEditUIVisibility();
goDoCommand("cmd_paste");
waitForSuccess(waitForPaste);
}
let waitForPaste = {
name: "no completion value after paste",
validatorFn: function()
{
return !jsterm.completeNode.value;
},
successFn: onClipboardPaste,
failureFn: finishTest,
};
function onClipboardPaste() {
goDoCommand("cmd_undo");
waitForSuccess({
name: "completion value for 'docu' after undo",
validatorFn: function()
{
return !!jsterm.completeNode.value;
},
finish);
}, false);
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");
jsterm.clearOutput();
let msg = "foo";
jsterm.execute("$0.textContent = '" + msg + "'");
findLogEntry(msg);
is(InspectorUI.selection.textContent, msg, "node successfully updated");
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");
doc = h1 = null;
executeSoon(finishUp);
jsterm.clearOutput();
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");
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);
is(jsterm.completeNode.value, " ment", "'docu' completion");
gBrowser.removeCurrentTab();
executeSoon(finishTest);
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, function() {
is(jsterm.completeNode.value, " ment", "'docu' completion");
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;");
let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
is(nodes.length, 2, "Two children in output");
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(/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),
"command was executed in the window scope");
isnot(nodes[1].textContent.indexOf(TEST_URI), -1,
"command was executed in the window scope");
executeSoon(finishTest);
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(' '); }");
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();
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");
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,
"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();
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'");
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) {
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);
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");
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;");
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");
waitForSuccess({
name: "null 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("null"), -1,
"'null' printed to output");
jsterm.clearOutput();
jsterm.execute("undefined;");
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,56 +6,63 @@
// 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", [
1,
/abc/,
null,
undefined,
function 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", {
"0.02": 0,
"0.01": 1,
"02": 2,
"1": 3,
"11": 4,
"1.2": 5,
"1.1": 6,
"foo": 7,
"bar": 8
propPanel = jsterm.openPropertyPanel({
data: {
object: {
"0.02": 0,
"0.01": 1,
"02": 2,
"1": 3,
"11": 4,
"1.2": 5,
"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.