backout bugs 586514, 573102, 585195. a=bsmedberg

This commit is contained in:
Rob Campbell 2010-08-13 15:49:56 -03:00
parent 06e3d2ec5c
commit 1a5a502390
6 changed files with 14 additions and 716 deletions

View File

@ -63,17 +63,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "sss",
"@mozilla.org/content/style-sheet-service;1",
"nsIStyleSheetService");
XPCOMUtils.defineLazyGetter(this, "PropertyPanel", function () {
var obj = {};
try {
Cu.import("resource://gre/modules/PropertyPanel.jsm", obj);
} catch (err) {
Cu.reportError(err);
}
return obj.PropertyPanel;
});
function LogFactory(aMessagePrefix)
{
function log(aMessage) {
@ -2490,38 +2479,25 @@ JSTerm.prototype = {
{
return this.context.get().QueryInterface(Ci.nsIDOMWindowInternal);
},
/**
* Evaluates a string in the sandbox. The string is currently wrapped by a
* with(window) { aString } construct, see bug 574033.
*
* @param string aString
* String to evaluate in the sandbox.
* @returns something
* The result of the evaluation.
*/
evalInSandbox: function JST_evalInSandbox(aString)
{
let execStr = "with(window) {" + aString + "}";
return Cu.evalInSandbox(execStr, this.sandbox, "default", "HUD Console", 1);
},
execute: function JST_execute(aExecuteString)
{
// attempt to execute the content of the inputNode
aExecuteString = aExecuteString || this.inputNode.value;
if (!aExecuteString) {
var str = aExecuteString || this.inputNode.value;
if (!str) {
this.console.log("no value to execute");
return;
}
this.writeOutput(aExecuteString, true);
this.writeOutput(str, true);
try {
var result = this.evalInSandbox(aExecuteString);
var execStr = "with(window) {" + str + "}";
var result =
Cu.evalInSandbox(execStr, this.sandbox, "default", "HUD Console", 1);
if (result || result === false) {
this.writeOutputJS(aExecuteString, result);
if (result || result === false || result === " ") {
this.writeOutput(result, false);
}
else if (result === undefined) {
this.writeOutput("undefined", false);
@ -2531,122 +2507,17 @@ JSTerm.prototype = {
}
}
catch (ex) {
this.console.error(ex);
if (ex) {
this.console.error(ex);
}
}
this.history.push(aExecuteString);
this.history.push(str);
this.historyIndex++;
this.historyPlaceHolder = this.history.length;
this.inputNode.value = "";
},
/**
* Opens a new PropertyPanel. The panel has two buttons: "Update" reexecutes
* the passed aEvalString and places the result inside of the tree. The other
* button closes the panel.
*
* @param string aEvalString
* String that was used to eval the aOutputObject. Used as title
* and to update the tree content.
* @param object aOutputObject
* Object to display/inspect inside of the tree.
* @param nsIDOMNode aAnchor
* A node to popup the panel next to (using "after_pointer").
* @returns object the created and opened propertyPanel.
*/
openPropertyPanel: function JST_openPropertyPanel(aEvalString, aOutputObject,
aAnchor)
{
let self = this;
let propPanel;
// The property panel has two buttons:
// 1. `Update`: reexecutes the string executed on the command line. The
// result will be inspected by this panel.
// 2. `Close`: destroys the panel.
let buttons = [];
// If there is a evalString passed to this function, then add a `Update`
// button to the panel so that the evalString can be reexecuted to update
// the content of the panel.
if (aEvalString !== null) {
buttons.push({
label: HUDService.getStr("update.button"),
accesskey: HUDService.getStr("update.accesskey"),
oncommand: function () {
try {
var result = self.evalInSandbox(aEvalString);
if (result !== undefined) {
// TODO: This updates the value of the tree.
// However, the states of opened nodes is not saved.
// See bug 586246.
propPanel.treeView.data = result;
}
}
catch (ex) {
self.console.error(ex);
}
}
});
}
buttons.push({
label: HUDService.getStr("close.button"),
accesskey: HUDService.getStr("close.accesskey"),
oncommand: function () {
propPanel.destroy();
}
});
let doc = self.parentNode.ownerDocument;
let parent = doc.getElementById("mainPopupSet");
let title = (aEvalString
? HUDService.getFormatStr("jsPropertyInspectTitle", [aEvalString])
: HUDService.getStr("jsPropertyTitle"));
propPanel = new PropertyPanel(parent, doc, title, aOutputObject, buttons);
let panel = propPanel.panel;
panel.openPopup(aAnchor, "after_pointer", 0, 0, false, false);
panel.sizeTo(200, 400);
return propPanel;
},
/**
* Writes a JS object to the JSTerm outputNode. If the user clicks on the
* written object, openPropertyPanel is called to open up a panel to inspect
* the object.
*
* @param string aEvalString
* String that was evaluated to get the aOutputObject.
* @param object aOutputObject
* Object to be written to the outputNode.
*/
writeOutputJS: function JST_writeOutputJS(aEvalString, aOutputObject)
{
let lastGroupNode = HUDService.appendGroupIfNecessary(this.outputNode,
Date.now());
var node = this.elementFactory("div");
node.setAttribute("class", "jsterm-output-line");
var self = this;
var link = this.elementFactory("a");
link.setAttribute("href", "javascript:");
link.setAttribute("aria-haspopup", "true");
link.onclick = function() {
self.openPropertyPanel(aEvalString, aOutputObject, link);
}
// TODO: format the aOutputObject and don't just use the
// aOuputObject.toString() function: [object object] -> Object {prop, ...}
// See bug 586249.
let textNode = this.textFactory(aOutputObject);
link.appendChild(textNode);
node.appendChild(link);
lastGroupNode.appendChild(node);
node.scrollIntoView(false);
},
/**
* Writes a message to the HUD that originates from the interactive
* JavaScript console.

View File

@ -46,7 +46,6 @@ include $(DEPTH)/config/autoconf.mk
MODULE = hudservice
EXTRA_JS_MODULES = HUDService.jsm \
PropertyPanel.jsm \
$(NULL)
ifdef ENABLE_TESTS

View File

@ -1,504 +0,0 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is DevTools (HeadsUpDisplay) Console Code
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Rob Campbell <rcampbell@mozilla.com>
* Julian Viereck <jviereck@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
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 = ["PropertyPanel", "PropertyTreeView"];
///////////////////////////////////////////////////////////////////////////
//// Helper for 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 "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);
let display;
return {
type: TYPE_OBJECT,
display: m ? m[1] : "Object"
};
}
}
/**
* 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;
for (var propName in aObject) {
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:
* https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsITreeView
*/
var PropertyTreeView = function() {
this._rows = [];
};
PropertyTreeView.prototype = {
/**
* Stores the visible rows of the tree.
*/
_rows: null,
/**
* Stores the nsITreeBoxObject for this tree.
*/
_treeBox: 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
*/
set data(aObject) {
let oldLen = this._rows.length;
this._rows = this.getChildItems(aObject, true);
if (this._treeBox) {
this._treeBox.beginUpdateBatch();
if (oldLen) {
this._treeBox.rowCountChanged(0, -oldLen);
}
this._treeBox.rowCountChanged(0, this._rows.length);
this._treeBox.endUpdateBatch();
}
},
/**
* 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.
*
* @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.
*/
getChildItems: function(aItem, aRootElement)
{
// 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;
}
let pairs;
let newPairLevel;
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;
},
/** nsITreeView interface implementation **/
selection: null,
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; },
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)
{
var item = this._rows[idx];
if (!item.children) {
return;
}
this._treeBox.beginUpdateBatch();
if (item.isOpened) {
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);
}
}
else {
item.isOpened = true;
var toInsert = this.getChildItems(item);
item.children = toInsert;
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(toInsert));
this._treeBox.rowCountChanged(idx + 1, toInsert.length);
}
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
},
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; }
};
///////////////////////////////////////////////////////////////////////////
//// 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.
*
* @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.
* @param array of objects aButtons
* Array with buttons to display at the bottom of the panel.
*/
function PropertyPanel(aParent, aDocument, aTitle, aObject, aButtons)
{
// Create the underlying panel
this.panel = createElement(aDocument, "panel", {
label: aTitle,
titlebar: "normal",
noautofocus: "true",
noautohide: "true"
});
// Create the tree.
let tree = this.tree = createElement(aDocument, "tree", { flex: 1 });
let treecols = aDocument.createElement("treecols");
appendChild(aDocument, treecols, "treecol", {
primary: "true",
flex: 1
});
tree.appendChild(treecols);
tree.appendChild(aDocument.createElement("treechildren"));
this.panel.appendChild(tree);
// Create the footer.
let footer = createElement(aDocument, "hbox", { align: "end" });
appendChild(aDocument, footer, "spacer", { flex: 1 });
// The footer can have butttons.
if (aButtons) {
aButtons.forEach(function(button) {
let buttonNode = appendChild(aDocument, footer, "button", {
label: button.label,
accesskey: button.accesskey || ""
});
button.dom = buttonNode;
buttonNode.addEventListener("command", button.oncommand, false);
});
this.panel.addEventListener("popuphidden", function onPopupHide(e)
{
event.target.removeEventListener("popuphidden", arguments.callee, false);
aButtons.forEach(function(button) {
button.dom.removeEventListener("command", button.oncommand, false);
});
}, false);
}
appendChild(aDocument, 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);
}
/**
* Destroy the PropertyPanel. This closes the poped up panel and removes
* it from the browser DOM.
*
* @returns void
*/
PropertyPanel.prototype.destroy = function PP_destroy()
{
this.panel.hidePopup();
this.panel.parentNode.removeChild(this.panel);
this.panel = null;
this.treeView = null;
this.tree = null;
}

View File

@ -385,7 +385,7 @@ function testJSInputAndOutputStyling() {
"JS input node is of the CSS class 'jsterm-input-line'");
let jsOutputNode = outputChildren[2];
isnot(jsOutputNode.childNodes[0].textContent.indexOf("4"), -1,
isnot(jsOutputNode.childNodes[0].nodeValue.indexOf("4"), -1,
"JS output node contains '4'");
isnot(jsOutputNode.getAttribute("class").indexOf("jsterm-output-line"), -1,
"JS output node is of the CSS class 'jsterm-output-line'");
@ -624,52 +624,10 @@ function testExecutionScope()
is(/location;/.test(outputChildren[1].childNodes[0].nodeValue), true,
"'location;' written to output");
isnot(outputChildren[2].childNodes[0].textContent.indexOf(TEST_URI), -1,
isnot(outputChildren[2].childNodes[0].nodeValue.indexOf(TEST_URI), -1,
"command was executed in the window scope");
}
function testPropertyPanel()
{
var HUD = HUDService.hudWeakReferences[hudId].get();
var jsterm = HUD.jsterm;
let propPanel = jsterm.openPropertyPanel("Test", [
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
});
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");
propPanel.destroy();
}
function testIteration() {
var id = "foo";
var it = cs.displayStore(id);
@ -920,7 +878,6 @@ function test() {
testCompletion();
testPropertyProvider();
testJSInputExpand();
testPropertyPanel();
testNet();
});
}, false);

View File

@ -36,22 +36,5 @@ localConsole=Local Console
btnClear=Clear Console
tipClear=Clear the console output
stringFilter=Filter
close.button=Close
close.accesskey=C
update.button=Update
update.accesskey=U
# LOCALIZATION NOTE FOR `jsPropertyTitle` AND `jsPropertyInspectTitle`:
#
# The "PropertyPanel" is used to display a JS object to the user.
# If it is clear which object is being inspected (e.g., window, document object)
# the title of the panel is based on the `jsPropertyInspectTitle` string.
# If it isn't clear which object is being inspected, the `jsPropertyTitle` string
# gets used. This can be the case when the user logs an object to the WebConsole
# output using the console.log(aObjectToInspect) method.
#
# You can find a screenshot of the PropertyPanel here:
# https://bug585030.bugzilla.mozilla.org/attachment.cgi?id=464034
jsPropertyTitle=Object Inspector
jsPropertyInspectTitle=Inspect: %S
copyCmd.label=Copy
copyCmd.accesskey=C

View File

@ -168,11 +168,3 @@
padding: 1px 0px;
-moz-box-align: center;
}
.hud-console-filter-toolbar:-moz-system-metric(windows-default-theme) {
background: -moz-linear-gradient(top, #f2f7fd 0, #e9f2fc 12px, #e3eefb 12px, #e3eefb 100%);
border-top: 1px solid #fff;
border-bottom: 2px solid ThreeDShadow;
-moz-appearance: none !important;
}