2012-10-26 10:10:17 -07:00
|
|
|
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
|
2012-11-17 11:41:04 -08:00
|
|
|
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
|
2012-10-26 13:28:54 -07:00
|
|
|
const LAZY_EMPTY_DELAY = 150; // ms
|
2012-11-21 00:49:45 -08:00
|
|
|
const SEARCH_ACTION_MAX_DELAY = 1000; // ms
|
2012-10-26 13:28:54 -07:00
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
Components.utils.import('resource://gre/modules/Services.jsm');
|
2012-11-17 11:41:04 -08:00
|
|
|
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-11-10 03:14:40 -08:00
|
|
|
this.EXPORTED_SYMBOLS = ["VariablesView", "create"];
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A tree view for inspecting scopes, objects and properties.
|
|
|
|
* Iterable via "for (let [id, scope] in instance) { }".
|
|
|
|
* Requires the devtools common.css and debugger.css skin stylesheets.
|
|
|
|
*
|
|
|
|
* To allow replacing variable or property values in this view, provide an
|
2012-11-27 08:19:23 -08:00
|
|
|
* "eval" function property. To allow replacing variable or property values,
|
|
|
|
* provide a "switch" function. To handle deleting variables or properties,
|
|
|
|
* provide a "delete" function.
|
2012-10-26 10:10:17 -07:00
|
|
|
*
|
|
|
|
* @param nsIDOMNode aParentNode
|
|
|
|
* The parent node to hold this view.
|
|
|
|
*/
|
2012-11-10 03:14:40 -08:00
|
|
|
this.VariablesView = function VariablesView(aParentNode) {
|
2012-10-26 10:10:17 -07:00
|
|
|
this._store = new Map();
|
|
|
|
this._prevHierarchy = new Map();
|
|
|
|
this._currHierarchy = new Map();
|
|
|
|
this._parent = aParentNode;
|
|
|
|
this._appendEmptyNotice();
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
this._onSearchboxInput = this._onSearchboxInput.bind(this);
|
|
|
|
this._onSearchboxKeyPress = this._onSearchboxKeyPress.bind(this);
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
// Create an internal list container.
|
|
|
|
this._list = this.document.createElement("vbox");
|
|
|
|
this._parent.appendChild(this._list);
|
|
|
|
}
|
|
|
|
|
|
|
|
VariablesView.prototype = {
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Helper setter for populating this container with a raw object.
|
|
|
|
*
|
|
|
|
* @param object aData
|
|
|
|
* The raw object to display. You can only provide this object
|
|
|
|
* if you want the variables view to work in sync mode.
|
|
|
|
*/
|
|
|
|
set rawObject(aObject) {
|
|
|
|
this.empty();
|
|
|
|
this.addScope().addVar().populate(aObject);
|
|
|
|
},
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Adds a scope to contain any inspected variables.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The scope's name (e.g. "Local", "Global" etc.).
|
|
|
|
* @return Scope
|
|
|
|
* The newly created Scope instance.
|
|
|
|
*/
|
2012-11-03 16:01:05 -07:00
|
|
|
addScope: function VV_addScope(aName = "") {
|
2012-10-26 10:10:17 -07:00
|
|
|
this._removeEmptyNotice();
|
2012-12-10 23:38:00 -08:00
|
|
|
this._toggleSearch(true);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
let scope = new Scope(this, aName);
|
|
|
|
this._store.set(scope.id, scope);
|
2012-10-26 13:28:54 -07:00
|
|
|
this._currHierarchy.set(aName, scope);
|
2012-11-03 16:01:05 -07:00
|
|
|
scope.header = !!aName;
|
2012-10-26 10:10:17 -07:00
|
|
|
return scope;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all items from this container.
|
2012-10-26 13:28:54 -07:00
|
|
|
*
|
|
|
|
* @param number aTimeout [optional]
|
|
|
|
* The number of milliseconds to delay the operation if
|
|
|
|
* lazy emptying of this container is enabled.
|
2012-10-26 10:10:17 -07:00
|
|
|
*/
|
2012-10-26 13:28:54 -07:00
|
|
|
empty: function VV_empty(aTimeout = LAZY_EMPTY_DELAY) {
|
|
|
|
// If there are no items in this container, emptying is useless.
|
2012-11-02 08:12:36 -07:00
|
|
|
if (!this._store.size) {
|
2012-10-26 13:28:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check if this empty operation may be executed lazily.
|
|
|
|
if (this.lazyEmpty && aTimeout > 0) {
|
|
|
|
this._emptySoon(aTimeout);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
let list = this._list;
|
|
|
|
let firstChild;
|
|
|
|
|
|
|
|
while (firstChild = list.firstChild) {
|
|
|
|
list.removeChild(firstChild);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._store = new Map();
|
|
|
|
this._appendEmptyNotice();
|
2012-12-10 23:38:00 -08:00
|
|
|
this._toggleSearch(false);
|
2012-10-26 10:10:17 -07:00
|
|
|
},
|
|
|
|
|
2012-10-26 13:28:54 -07:00
|
|
|
/**
|
|
|
|
* Emptying this container and rebuilding it immediately afterwards would
|
|
|
|
* result in a brief redraw flicker, because the previously expanded nodes
|
|
|
|
* may get asynchronously re-expanded, after fetching the prototype and
|
|
|
|
* properties from a server.
|
|
|
|
*
|
|
|
|
* To avoid such behaviour, a normal container list is rebuild, but not
|
|
|
|
* immediately attached to the parent container. The old container list
|
|
|
|
* is kept around for a short period of time, hopefully accounting for the
|
|
|
|
* data fetching delay. In the meantime, any operations can be executed
|
|
|
|
* normally.
|
|
|
|
*
|
|
|
|
* @see VariablesView.empty
|
|
|
|
* @see VariablesView.commitHierarchy
|
|
|
|
*/
|
|
|
|
_emptySoon: function VV__emptySoon(aTimeout) {
|
|
|
|
let window = this.window;
|
|
|
|
let document = this.document;
|
|
|
|
|
|
|
|
let prevList = this._list;
|
|
|
|
let currList = this._list = this.document.createElement("vbox");
|
|
|
|
this._store = new Map();
|
|
|
|
|
|
|
|
this._emptyTimeout = window.setTimeout(function() {
|
|
|
|
this._emptyTimeout = null;
|
|
|
|
|
|
|
|
this._parent.removeChild(prevList);
|
|
|
|
this._parent.appendChild(currList);
|
|
|
|
|
2012-11-02 08:12:36 -07:00
|
|
|
if (!this._store.size) {
|
2012-10-26 13:28:54 -07:00
|
|
|
this._appendEmptyNotice();
|
2012-12-10 23:38:00 -08:00
|
|
|
this._toggleSearch(false);
|
2012-10-26 13:28:54 -07:00
|
|
|
}
|
|
|
|
}.bind(this), aTimeout);
|
|
|
|
},
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Specifies if enumerable properties and variables should be displayed.
|
2012-12-04 05:48:39 -08:00
|
|
|
* These variables and properties are visible by default.
|
2012-10-26 10:10:17 -07:00
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set enumVisible(aFlag) {
|
|
|
|
this._enumVisible = aFlag;
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
for (let [, scope] in this) {
|
2012-11-17 11:41:04 -08:00
|
|
|
scope._enumVisible = aFlag;
|
2012-10-26 10:10:17 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies if non-enumerable properties and variables should be displayed.
|
2012-12-04 05:48:39 -08:00
|
|
|
* These variables and properties are visible by default.
|
2012-10-26 10:10:17 -07:00
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set nonEnumVisible(aFlag) {
|
|
|
|
this._nonEnumVisible = aFlag;
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
for (let [, scope] in this) {
|
2012-10-26 10:10:17 -07:00
|
|
|
scope._nonEnumVisible = aFlag;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-12-04 05:48:39 -08:00
|
|
|
/**
|
|
|
|
* Specifies if only enumerable properties and variables should be displayed.
|
|
|
|
* Both types of these variables and properties are visible by default.
|
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set onlyEnumVisible(aFlag) {
|
|
|
|
if (aFlag) {
|
|
|
|
this.enumVisible = true;
|
|
|
|
this.nonEnumVisible = false;
|
|
|
|
} else {
|
|
|
|
this.enumVisible = true;
|
|
|
|
this.nonEnumVisible = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Enables variable and property searching in this view.
|
|
|
|
*/
|
|
|
|
enableSearch: function VV_enableSearch() {
|
|
|
|
// If searching was already enabled, no need to re-enable it again.
|
|
|
|
if (this._searchboxContainer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let document = this.document;
|
2012-12-10 23:38:00 -08:00
|
|
|
let ownerView = this._parent.parentNode;
|
2012-11-03 16:01:05 -07:00
|
|
|
|
|
|
|
let container = this._searchboxContainer = document.createElement("hbox");
|
|
|
|
container.className = "devtools-toolbar";
|
2012-12-10 23:38:00 -08:00
|
|
|
container.hidden = !this._store.size;
|
2012-11-03 16:01:05 -07:00
|
|
|
|
|
|
|
let searchbox = this._searchboxNode = document.createElement("textbox");
|
2012-12-10 23:38:00 -08:00
|
|
|
searchbox.className = "variables-searchinput devtools-searchinput";
|
2012-11-03 16:01:05 -07:00
|
|
|
searchbox.setAttribute("placeholder", this._searchboxPlaceholder);
|
|
|
|
searchbox.setAttribute("type", "search");
|
|
|
|
searchbox.setAttribute("flex", "1");
|
|
|
|
searchbox.addEventListener("input", this._onSearchboxInput, false);
|
|
|
|
searchbox.addEventListener("keypress", this._onSearchboxKeyPress, false);
|
|
|
|
|
|
|
|
container.appendChild(searchbox);
|
2012-12-10 23:38:00 -08:00
|
|
|
ownerView.insertBefore(container, this._parent);
|
2012-11-03 16:01:05 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disables variable and property searching in this view.
|
|
|
|
*/
|
|
|
|
disableSearch: function VV_disableSearch() {
|
|
|
|
// If searching was already disabled, no need to re-disable it again.
|
|
|
|
if (!this._searchboxContainer) {
|
|
|
|
return;
|
|
|
|
}
|
2012-12-10 23:38:00 -08:00
|
|
|
this._searchboxContainer.parentNode.removeChild(this._searchboxContainer);
|
2012-11-03 16:01:05 -07:00
|
|
|
this._searchboxNode.addEventListener("input", this._onSearchboxInput, false);
|
|
|
|
this._searchboxNode.addEventListener("keypress", this._onSearchboxKeyPress, false);
|
|
|
|
|
|
|
|
this._searchboxContainer = null;
|
|
|
|
this._searchboxNode = null;
|
|
|
|
},
|
|
|
|
|
2012-12-10 23:38:00 -08:00
|
|
|
/**
|
|
|
|
* Sets the variables searchbox hidden or visible. It's hidden by default.
|
|
|
|
*
|
|
|
|
* @param boolean aVisibleFlag
|
|
|
|
* Specifies the intended visibility.
|
|
|
|
*/
|
|
|
|
_toggleSearch: function VV__toggleSearch(aVisibleFlag) {
|
|
|
|
// If searching was already disabled, there's no need to hide it.
|
|
|
|
if (!this._searchboxContainer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._searchboxContainer.hidden = !aVisibleFlag;
|
|
|
|
},
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Sets if the variable and property searching is enabled.
|
|
|
|
*/
|
|
|
|
set searchEnabled(aFlag) aFlag ? this.enableSearch() : this.disableSearch(),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the variable and property searching is enabled.
|
|
|
|
*/
|
|
|
|
get searchEnabled() !!this._searchboxContainer,
|
|
|
|
|
2012-11-21 00:49:45 -08:00
|
|
|
/**
|
|
|
|
* Allows searches to be scheduled and delayed to avoid redundant calls.
|
|
|
|
*/
|
|
|
|
delayedSearch: true,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedules searching for variables or properties matching the query.
|
|
|
|
*
|
|
|
|
* @param string aQuery
|
|
|
|
* The variable or property to search for.
|
|
|
|
*/
|
|
|
|
scheduleSearch: function VV_scheduleSearch(aQuery) {
|
|
|
|
if (!this.delayedSearch) {
|
|
|
|
this.performSearch(aQuery);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let delay = Math.max(SEARCH_ACTION_MAX_DELAY / aQuery.length, 0);
|
|
|
|
|
|
|
|
this.window.clearTimeout(this._searchTimeout);
|
|
|
|
this._searchFunction = this._startSearch.bind(this, aQuery);
|
|
|
|
this._searchTimeout = this.window.setTimeout(this._searchFunction, delay);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Immediately searches for variables or properties matching the query.
|
|
|
|
*
|
|
|
|
* @param string aQuery
|
|
|
|
* The variable or property to search for.
|
|
|
|
*/
|
|
|
|
performSearch: function VV_performSearch(aQuery) {
|
|
|
|
this.window.clearTimeout(this._searchTimeout);
|
|
|
|
this._searchFunction = null;
|
|
|
|
this._startSearch(aQuery);
|
|
|
|
},
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Performs a case insensitive search for variables or properties matching
|
|
|
|
* the query, and hides non-matched items.
|
|
|
|
*
|
|
|
|
* @param string aQuery
|
|
|
|
* The variable or property to search for.
|
|
|
|
*/
|
2012-11-21 00:49:45 -08:00
|
|
|
_startSearch: function VV__startSearch(aQuery) {
|
2012-11-15 22:34:13 -08:00
|
|
|
for (let [, scope] in this) {
|
|
|
|
switch (aQuery) {
|
|
|
|
case "":
|
|
|
|
scope.expand();
|
|
|
|
// fall through
|
|
|
|
case null:
|
|
|
|
scope._performSearch("");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
scope._performSearch(aQuery.toLowerCase());
|
|
|
|
break;
|
2012-11-03 16:01:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2012-11-03 16:01:05 -07:00
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Expands the first search results in this container.
|
|
|
|
*/
|
|
|
|
expandFirstSearchResults: function VV_expandFirstSearchResults() {
|
2012-11-15 22:34:13 -08:00
|
|
|
for (let [, scope] in this) {
|
|
|
|
for (let [, variable] in scope) {
|
2012-11-03 16:01:05 -07:00
|
|
|
if (variable._isMatch) {
|
|
|
|
variable.expand();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-11-03 16:01:05 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the text displayed for the searchbox in this container.
|
|
|
|
* @param string aValue
|
|
|
|
*/
|
|
|
|
set searchPlaceholder(aValue) {
|
|
|
|
if (this._searchboxNode) {
|
|
|
|
this._searchboxNode.setAttribute("placeholder", aValue);
|
|
|
|
}
|
|
|
|
this._searchboxPlaceholder = aValue;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the searchbox input event.
|
|
|
|
*/
|
|
|
|
_onSearchboxInput: function VV__onSearchboxInput() {
|
|
|
|
this.performSearch(this._searchboxNode.value);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listener handling the searchbox key press event.
|
|
|
|
*/
|
|
|
|
_onSearchboxKeyPress: function VV__onSearchboxKeyPress(e) {
|
|
|
|
switch(e.keyCode) {
|
|
|
|
case e.DOM_VK_RETURN:
|
|
|
|
case e.DOM_VK_ENTER:
|
|
|
|
this._onSearchboxInput();
|
|
|
|
return;
|
|
|
|
case e.DOM_VK_ESCAPE:
|
|
|
|
this._searchboxNode.value = "";
|
|
|
|
this._onSearchboxInput();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Sets the text displayed in this container when there are no available items.
|
|
|
|
* @param string aValue
|
|
|
|
*/
|
|
|
|
set emptyText(aValue) {
|
|
|
|
if (this._emptyTextNode) {
|
|
|
|
this._emptyTextNode.setAttribute("value", aValue);
|
|
|
|
}
|
|
|
|
this._emptyTextValue = aValue;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates and appends a label signaling that this container is empty.
|
|
|
|
*/
|
|
|
|
_appendEmptyNotice: function VV__appendEmptyNotice() {
|
|
|
|
if (this._emptyTextNode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let label = this.document.createElement("label");
|
|
|
|
label.className = "empty list-item";
|
|
|
|
label.setAttribute("value", this._emptyTextValue);
|
|
|
|
|
|
|
|
this._parent.appendChild(label);
|
|
|
|
this._emptyTextNode = label;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the label signaling that this container is empty.
|
|
|
|
*/
|
|
|
|
_removeEmptyNotice: function VV__removeEmptyNotice() {
|
|
|
|
if (!this._emptyTextNode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._parent.removeChild(this._emptyTextNode);
|
|
|
|
this._emptyTextNode = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the parent node holding this view.
|
|
|
|
* @return nsIDOMNode
|
|
|
|
*/
|
|
|
|
get parentNode() this._parent,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the owner document holding this view.
|
|
|
|
* @return nsIHTMLDocument
|
|
|
|
*/
|
|
|
|
get document() this._parent.ownerDocument,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the default window holding this view.
|
|
|
|
* @return nsIDOMWindow
|
|
|
|
*/
|
|
|
|
get window() this.document.defaultView,
|
|
|
|
|
|
|
|
eval: null,
|
2012-10-26 13:28:54 -07:00
|
|
|
lazyEmpty: false,
|
2012-10-26 10:10:17 -07:00
|
|
|
_store: null,
|
|
|
|
_prevHierarchy: null,
|
|
|
|
_currHierarchy: null,
|
2012-10-26 13:28:54 -07:00
|
|
|
_emptyTimeout: null,
|
2012-11-21 00:49:45 -08:00
|
|
|
_searchTimeout: null,
|
|
|
|
_searchFunction: null,
|
2012-10-26 10:10:17 -07:00
|
|
|
_enumVisible: true,
|
|
|
|
_nonEnumVisible: true,
|
|
|
|
_parent: null,
|
2012-11-03 16:01:05 -07:00
|
|
|
_list: null,
|
|
|
|
_searchboxNode: null,
|
|
|
|
_searchboxContainer: null,
|
|
|
|
_searchboxPlaceholder: "",
|
2012-10-26 10:10:17 -07:00
|
|
|
_emptyTextNode: null,
|
|
|
|
_emptyTextValue: ""
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Scope is an object holding Variable instances.
|
|
|
|
* Iterable via "for (let [name, variable] in instance) { }".
|
|
|
|
*
|
|
|
|
* @param VariablesView aView
|
|
|
|
* The view to contain this scope.
|
|
|
|
* @param string aName
|
|
|
|
* The scope's name.
|
|
|
|
* @param object aFlags [optional]
|
|
|
|
* Additional options or flags for this scope.
|
|
|
|
*/
|
|
|
|
function Scope(aView, aName, aFlags = {}) {
|
|
|
|
this.show = this.show.bind(this);
|
|
|
|
this.hide = this.hide.bind(this);
|
|
|
|
this.expand = this.expand.bind(this);
|
|
|
|
this.collapse = this.collapse.bind(this);
|
|
|
|
this.toggle = this.toggle.bind(this);
|
2012-11-15 22:34:13 -08:00
|
|
|
this._openEnum = this._openEnum.bind(this);
|
|
|
|
this._openNonEnum = this._openNonEnum.bind(this);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
this.ownerView = aView;
|
|
|
|
this.eval = aView.eval;
|
2012-11-27 08:19:23 -08:00
|
|
|
this.switch = aView.switch;
|
|
|
|
this.delete = aView.delete;
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
this._store = new Map();
|
|
|
|
this._init(aName.trim(), aFlags);
|
|
|
|
}
|
|
|
|
|
|
|
|
Scope.prototype = {
|
|
|
|
/**
|
|
|
|
* Adds a variable to contain any inspected properties.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The variable's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* Specifies the value and/or type & class of the variable,
|
|
|
|
* or 'get' & 'set' accessor properties. If the type is implicit,
|
|
|
|
* it will be inferred from the value.
|
|
|
|
* e.g. - { value: 42 }
|
|
|
|
* - { value: true }
|
|
|
|
* - { value: "nasu" }
|
|
|
|
* - { value: { type: "undefined" } }
|
|
|
|
* - { value: { type: "null" } }
|
|
|
|
* - { value: { type: "object", class: "Object" } }
|
|
|
|
* - { get: { type: "object", class: "Function" },
|
|
|
|
* set: { type: "undefined" } }
|
|
|
|
* @return Variable
|
|
|
|
* The newly created Variable instance, null if it already exists.
|
|
|
|
*/
|
2012-11-03 16:01:05 -07:00
|
|
|
addVar: function S_addVar(aName = "", aDescriptor = {}) {
|
2012-10-26 10:10:17 -07:00
|
|
|
if (this._store.has(aName)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let variable = new Variable(this, aName, aDescriptor);
|
|
|
|
this._store.set(aName, variable);
|
|
|
|
this._variablesView._currHierarchy.set(variable._absoluteName, variable);
|
2012-11-03 16:01:05 -07:00
|
|
|
variable.header = !!aName;
|
2012-10-26 10:10:17 -07:00
|
|
|
return variable;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the variable in this container having the specified name.
|
|
|
|
*
|
|
|
|
* @return Variable
|
|
|
|
* The matched variable, or null if nothing is found.
|
|
|
|
*/
|
|
|
|
get: function S_get(aName) {
|
|
|
|
return this._store.get(aName);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows the scope.
|
|
|
|
*/
|
|
|
|
show: function S_show() {
|
|
|
|
this._target.hidden = false;
|
|
|
|
this._isShown = true;
|
|
|
|
|
|
|
|
if (this.onshow) {
|
|
|
|
this.onshow(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the scope.
|
|
|
|
*/
|
|
|
|
hide: function S_hide() {
|
|
|
|
this._target.hidden = true;
|
|
|
|
this._isShown = false;
|
|
|
|
|
|
|
|
if (this.onhide) {
|
|
|
|
this.onhide(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expands the scope, showing all the added details.
|
|
|
|
*/
|
2012-11-15 22:34:13 -08:00
|
|
|
expand: function S_expand() {
|
2012-11-03 16:01:05 -07:00
|
|
|
if (this._isExpanded || this._locked) {
|
2012-10-26 10:10:17 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this._variablesView._enumVisible) {
|
2012-11-15 22:34:13 -08:00
|
|
|
this._openEnum();
|
2012-10-26 10:10:17 -07:00
|
|
|
}
|
|
|
|
if (this._variablesView._nonEnumVisible) {
|
2012-11-15 22:34:13 -08:00
|
|
|
Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0);
|
2012-10-26 10:10:17 -07:00
|
|
|
}
|
|
|
|
this._isExpanded = true;
|
|
|
|
|
|
|
|
if (this.onexpand) {
|
|
|
|
this.onexpand(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collapses the scope, hiding all the added details.
|
|
|
|
*/
|
|
|
|
collapse: function S_collapse() {
|
2012-11-03 16:01:05 -07:00
|
|
|
if (!this._isExpanded || this._locked) {
|
2012-10-26 10:10:17 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._arrow.removeAttribute("open");
|
|
|
|
this._enum.removeAttribute("open");
|
|
|
|
this._nonenum.removeAttribute("open");
|
|
|
|
this._isExpanded = false;
|
|
|
|
|
|
|
|
if (this.oncollapse) {
|
|
|
|
this.oncollapse(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles between the scope's collapsed and expanded state.
|
|
|
|
*/
|
2012-12-01 01:21:23 -08:00
|
|
|
toggle: function S_toggle(e) {
|
|
|
|
if (e && e.button != 0) {
|
|
|
|
// Only allow left-click to trigger this event.
|
|
|
|
return;
|
|
|
|
}
|
2012-11-03 16:01:05 -07:00
|
|
|
this._wasToggled = true;
|
2012-10-26 10:10:17 -07:00
|
|
|
this.expanded ^= 1;
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
// Make sure the scope and its contents are visibile.
|
|
|
|
for (let [, variable] in this) {
|
|
|
|
variable.header = true;
|
|
|
|
variable._match = true;
|
|
|
|
}
|
2012-10-26 10:10:17 -07:00
|
|
|
if (this.ontoggle) {
|
|
|
|
this.ontoggle(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Shows the scope's title header.
|
|
|
|
*/
|
|
|
|
showHeader: function S_showHeader() {
|
2012-11-15 22:34:13 -08:00
|
|
|
if (this._isHeaderVisible) {
|
|
|
|
return;
|
|
|
|
}
|
2012-11-03 16:01:05 -07:00
|
|
|
this._target.removeAttribute("non-header");
|
|
|
|
this._isHeaderVisible = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the scope's title header.
|
|
|
|
* This action will automatically expand the scope.
|
|
|
|
*/
|
|
|
|
hideHeader: function S_hideHeader() {
|
2012-11-15 22:34:13 -08:00
|
|
|
if (!this._isHeaderVisible) {
|
|
|
|
return;
|
|
|
|
}
|
2012-11-03 16:01:05 -07:00
|
|
|
this.expand();
|
|
|
|
this._target.setAttribute("non-header", "");
|
|
|
|
this._isHeaderVisible = false;
|
|
|
|
},
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Shows the scope's expand/collapse arrow.
|
|
|
|
*/
|
|
|
|
showArrow: function S_showArrow() {
|
2012-11-15 22:34:13 -08:00
|
|
|
if (this._isArrowVisible) {
|
|
|
|
return;
|
|
|
|
}
|
2012-10-26 10:10:17 -07:00
|
|
|
this._arrow.removeAttribute("invisible");
|
|
|
|
this._isArrowVisible = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the scope's expand/collapse arrow.
|
|
|
|
*/
|
|
|
|
hideArrow: function S_hideArrow() {
|
2012-11-15 22:34:13 -08:00
|
|
|
if (!this._isArrowVisible) {
|
|
|
|
return;
|
|
|
|
}
|
2012-10-26 10:10:17 -07:00
|
|
|
this._arrow.setAttribute("invisible", "");
|
|
|
|
this._isArrowVisible = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the visibility state.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
get visible() this._isShown,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the expanded state.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
get expanded() this._isExpanded,
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Gets the header visibility state.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
get header() this._isHeaderVisible,
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Gets the twisty visibility state.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
get twisty() this._isArrowVisible,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the visibility state.
|
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set visible(aFlag) aFlag ? this.show() : this.hide(),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the expanded state.
|
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set expanded(aFlag) aFlag ? this.expand() : this.collapse(),
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Sets the header visibility state.
|
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set header(aFlag) aFlag ? this.showHeader() : this.hideHeader(),
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Sets the twisty visibility state.
|
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set twisty(aFlag) aFlag ? this.showArrow() : this.hideArrow(),
|
|
|
|
|
2012-12-10 23:36:50 -08:00
|
|
|
/**
|
|
|
|
* Specifies if the configurable/enumerable/writable tooltip should be shown
|
|
|
|
* whenever a variable or property descriptor is available.
|
|
|
|
* This flag applies non-recursively to the current scope.
|
|
|
|
*/
|
|
|
|
showDescriptorTooltip: true,
|
|
|
|
|
2012-11-27 08:19:23 -08:00
|
|
|
/**
|
|
|
|
* Specifies if editing variable or property names is allowed.
|
|
|
|
* This flag applies non-recursively to the current scope.
|
|
|
|
*/
|
|
|
|
allowNameInput: false,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies if editing variable or property values is allowed.
|
|
|
|
* This flag applies non-recursively to the current scope.
|
|
|
|
*/
|
|
|
|
allowValueInput: true,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies if removing variables or properties values is allowed.
|
|
|
|
* This flag applies non-recursively to the current scope.
|
|
|
|
*/
|
|
|
|
allowDeletion: false,
|
|
|
|
|
2012-12-01 01:21:23 -08:00
|
|
|
/**
|
|
|
|
* Specifies the context menu attribute set on variables and properties.
|
|
|
|
*/
|
|
|
|
contextMenu: "",
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Gets the id associated with this item.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
get id() this._idString,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the name associated with this item.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
get name() this._nameString,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the element associated with this item.
|
|
|
|
* @return nsIDOMNode
|
|
|
|
*/
|
|
|
|
get target() this._target,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes this scope's id, view and binds event listeners.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The scope's name.
|
|
|
|
* @param object aFlags [optional]
|
|
|
|
* Additional options or flags for this scope.
|
|
|
|
* @param string aClassName [optional]
|
|
|
|
* A custom class name for this scope.
|
|
|
|
*/
|
|
|
|
_init: function S__init(aName, aFlags = {}, aClassName = "scope") {
|
|
|
|
this._idString = generateId(this._nameString = aName);
|
|
|
|
this._createScope(aName, aClassName);
|
|
|
|
this._addEventListeners();
|
|
|
|
this.parentNode.appendChild(this._target);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates the necessary nodes for this scope.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The scope's name.
|
|
|
|
* @param string aClassName
|
|
|
|
* A custom class name for this scope.
|
|
|
|
*/
|
|
|
|
_createScope: function S__createScope(aName, aClassName) {
|
|
|
|
let document = this.document;
|
|
|
|
|
|
|
|
let element = this._target = document.createElement("vbox");
|
2012-12-04 05:48:39 -08:00
|
|
|
element.id = this._idString;
|
2012-10-26 10:10:17 -07:00
|
|
|
element.className = aClassName;
|
|
|
|
|
|
|
|
let arrow = this._arrow = document.createElement("hbox");
|
|
|
|
arrow.className = "arrow";
|
|
|
|
|
|
|
|
let name = this._name = document.createElement("label");
|
|
|
|
name.className = "name plain";
|
|
|
|
name.setAttribute("value", aName);
|
|
|
|
|
|
|
|
let title = this._title = document.createElement("hbox");
|
|
|
|
title.className = "title" + (aClassName == "scope" ? " devtools-toolbar" : "");
|
|
|
|
title.setAttribute("align", "center");
|
|
|
|
|
|
|
|
let enumerable = this._enum = document.createElement("vbox");
|
|
|
|
let nonenum = this._nonenum = document.createElement("vbox");
|
|
|
|
enumerable.className = "details";
|
|
|
|
nonenum.className = "details nonenum";
|
|
|
|
|
|
|
|
title.appendChild(arrow);
|
|
|
|
title.appendChild(name);
|
|
|
|
|
|
|
|
element.appendChild(title);
|
|
|
|
element.appendChild(enumerable);
|
|
|
|
element.appendChild(nonenum);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the necessary event listeners for this scope.
|
|
|
|
*/
|
|
|
|
_addEventListeners: function S__addEventListeners() {
|
|
|
|
this._title.addEventListener("mousedown", this.toggle, false);
|
|
|
|
},
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
/**
|
|
|
|
* Adds an event listener for the mouse over event on the title element.
|
|
|
|
* @param function aCallback
|
|
|
|
*/
|
|
|
|
set onmouseover(aCallback) {
|
|
|
|
this._title.addEventListener("mouseover", aCallback, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opens the enumerable items container.
|
|
|
|
*/
|
|
|
|
_openEnum: function S__openEnum() {
|
|
|
|
this._arrow.setAttribute("open", "");
|
|
|
|
this._enum.setAttribute("open", "");
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opens the non-enumerable items container.
|
|
|
|
*/
|
|
|
|
_openNonEnum: function S__openNonEnum() {
|
|
|
|
this._nonenum.setAttribute("open", "");
|
|
|
|
},
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Specifies if enumerable properties and variables should be displayed.
|
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set _enumVisible(aFlag) {
|
2012-11-15 22:34:13 -08:00
|
|
|
for (let [, variable] in this) {
|
2012-10-26 10:10:17 -07:00
|
|
|
variable._enumVisible = aFlag;
|
|
|
|
|
|
|
|
if (!this.expanded) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (aFlag) {
|
|
|
|
this._enum.setAttribute("open", "");
|
|
|
|
} else {
|
|
|
|
this._enum.removeAttribute("open");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies if non-enumerable properties and variables should be displayed.
|
|
|
|
* @param boolean aFlag
|
|
|
|
*/
|
|
|
|
set _nonEnumVisible(aFlag) {
|
2012-11-15 22:34:13 -08:00
|
|
|
for (let [, variable] in this) {
|
2012-10-26 10:10:17 -07:00
|
|
|
variable._nonEnumVisible = aFlag;
|
|
|
|
|
|
|
|
if (!this.expanded) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (aFlag) {
|
|
|
|
this._nonenum.setAttribute("open", "");
|
|
|
|
} else {
|
|
|
|
this._nonenum.removeAttribute("open");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Performs a case insensitive search for variables or properties matching
|
|
|
|
* the query, and hides non-matched items.
|
|
|
|
*
|
|
|
|
* @param string aLowerCaseQuery
|
|
|
|
* The lowercased name of the variable or property to search for.
|
|
|
|
*/
|
|
|
|
_performSearch: function S__performSearch(aLowerCaseQuery) {
|
2012-11-15 22:34:13 -08:00
|
|
|
for (let [, variable] in this) {
|
2012-11-03 16:01:05 -07:00
|
|
|
let currentObject = variable;
|
|
|
|
let lowerCaseName = variable._nameString.toLowerCase();
|
|
|
|
let lowerCaseValue = variable._valueString.toLowerCase();
|
|
|
|
|
|
|
|
// Non-matched variables or properties require a corresponding attribute.
|
|
|
|
if (!lowerCaseName.contains(aLowerCaseQuery) &&
|
|
|
|
!lowerCaseValue.contains(aLowerCaseQuery)) {
|
2012-11-03 16:01:05 -07:00
|
|
|
variable._match = false;
|
2012-11-03 16:01:05 -07:00
|
|
|
}
|
|
|
|
// Variable or property is matched.
|
|
|
|
else {
|
2012-11-03 16:01:05 -07:00
|
|
|
variable._match = true;
|
2012-11-03 16:01:05 -07:00
|
|
|
|
|
|
|
// If the variable was ever expanded, there's a possibility it may
|
|
|
|
// contain some matched properties, so make sure they're visible
|
|
|
|
// ("expand downwards").
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
if (variable._wasToggled && aLowerCaseQuery) {
|
|
|
|
variable.expand();
|
|
|
|
}
|
|
|
|
if (variable._isExpanded && !aLowerCaseQuery) {
|
|
|
|
variable._wasToggled = true;
|
2012-11-03 16:01:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the variable is contained in another scope (variable or property),
|
|
|
|
// the parent may not be a match, thus hidden. It should be visible
|
|
|
|
// ("expand upwards").
|
|
|
|
|
|
|
|
while ((variable = variable.ownerView) && /* Parent object exists. */
|
|
|
|
(variable instanceof Scope ||
|
|
|
|
variable instanceof Variable ||
|
|
|
|
variable instanceof Property)) {
|
|
|
|
|
|
|
|
// Show and expand the parent, as it is certainly accessible.
|
2012-11-03 16:01:05 -07:00
|
|
|
variable._match = true;
|
2012-11-15 22:34:13 -08:00
|
|
|
aLowerCaseQuery && variable.expand();
|
2012-11-03 16:01:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Proceed with the search recursively inside this variable or property.
|
2012-11-15 22:34:13 -08:00
|
|
|
if (currentObject._wasToggled ||
|
|
|
|
currentObject.getter ||
|
|
|
|
currentObject.setter) {
|
2012-11-03 16:01:05 -07:00
|
|
|
currentObject._performSearch(aLowerCaseQuery);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets if this object instance is a match or non-match.
|
|
|
|
* @param boolean aStatus
|
|
|
|
*/
|
|
|
|
set _match(aStatus) {
|
|
|
|
if (this._isMatch == aStatus) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (aStatus) {
|
|
|
|
this._isMatch = true;
|
|
|
|
this.target.removeAttribute("non-match");
|
|
|
|
} else {
|
|
|
|
this._isMatch = false;
|
|
|
|
this.target.setAttribute("non-match", "");
|
2012-11-03 16:01:05 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Gets top level variables view instance.
|
|
|
|
* @return VariablesView
|
|
|
|
*/
|
|
|
|
get _variablesView() {
|
|
|
|
let parentView = this.ownerView;
|
|
|
|
let topView;
|
|
|
|
|
|
|
|
while (topView = parentView.ownerView) {
|
|
|
|
parentView = topView;
|
|
|
|
}
|
|
|
|
return parentView;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the parent node holding this scope.
|
|
|
|
* @return nsIDOMNode
|
|
|
|
*/
|
|
|
|
get parentNode() this.ownerView._list,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the owner document holding this scope.
|
|
|
|
* @return nsIHTMLDocument
|
|
|
|
*/
|
|
|
|
get document() this.ownerView.document,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the default window holding this scope.
|
|
|
|
* @return nsIDOMWindow
|
|
|
|
*/
|
|
|
|
get window() this.ownerView.window,
|
|
|
|
|
|
|
|
ownerView: null,
|
|
|
|
eval: null,
|
2012-11-03 16:01:05 -07:00
|
|
|
fetched: false,
|
2012-10-26 10:10:17 -07:00
|
|
|
_committed: false,
|
|
|
|
_locked: false,
|
|
|
|
_isShown: true,
|
|
|
|
_isExpanded: false,
|
2012-11-03 16:01:05 -07:00
|
|
|
_wasToggled: false,
|
2012-11-03 16:01:05 -07:00
|
|
|
_isHeaderVisible: true,
|
2012-10-26 10:10:17 -07:00
|
|
|
_isArrowVisible: true,
|
2012-11-03 16:01:05 -07:00
|
|
|
_isMatch: true,
|
2012-10-26 10:10:17 -07:00
|
|
|
_store: null,
|
2012-11-03 16:01:05 -07:00
|
|
|
_idString: "",
|
|
|
|
_nameString: "",
|
2012-10-26 10:10:17 -07:00
|
|
|
_target: null,
|
|
|
|
_arrow: null,
|
|
|
|
_name: null,
|
|
|
|
_title: null,
|
|
|
|
_enum: null,
|
|
|
|
_nonenum: null
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Variable is a Scope holding Property instances.
|
|
|
|
* Iterable via "for (let [name, property] in instance) { }".
|
|
|
|
*
|
|
|
|
* @param Scope aScope
|
|
|
|
* The scope to contain this varialbe.
|
|
|
|
* @param string aName
|
|
|
|
* The variable's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The variable's descriptor.
|
|
|
|
*/
|
|
|
|
function Variable(aScope, aName, aDescriptor) {
|
2012-11-27 08:19:23 -08:00
|
|
|
this._onClose = this._onClose.bind(this);
|
2012-11-15 22:34:13 -08:00
|
|
|
this._displayTooltip = this._displayTooltip.bind(this);
|
2012-11-27 08:19:23 -08:00
|
|
|
this._activateNameInput = this._activateNameInput.bind(this);
|
|
|
|
this._activateValueInput = this._activateValueInput.bind(this);
|
|
|
|
this._deactivateNameInput = this._deactivateNameInput.bind(this);
|
|
|
|
this._deactivateValueInput = this._deactivateValueInput.bind(this);
|
|
|
|
this._onNameInputKeyPress = this._onNameInputKeyPress.bind(this);
|
|
|
|
this._onValueInputKeyPress = this._onValueInputKeyPress.bind(this);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
Scope.call(this, aScope, aName, aDescriptor);
|
|
|
|
this._setGrip(aDescriptor.value);
|
|
|
|
this._symbolicName = aName;
|
|
|
|
this._absoluteName = aScope.name + "." + aName;
|
|
|
|
this._initialDescriptor = aDescriptor;
|
|
|
|
}
|
|
|
|
|
|
|
|
create({ constructor: Variable, proto: Scope.prototype }, {
|
|
|
|
/**
|
|
|
|
* Adds a property for this variable.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The property's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* Specifies the value and/or type & class of the property,
|
|
|
|
* or 'get' & 'set' accessor properties. If the type is implicit,
|
|
|
|
* it will be inferred from the value.
|
|
|
|
* e.g. - { value: 42 }
|
|
|
|
* - { value: true }
|
|
|
|
* - { value: "nasu" }
|
|
|
|
* - { value: { type: "undefined" } }
|
|
|
|
* - { value: { type: "null" } }
|
|
|
|
* - { value: { type: "object", class: "Object" } }
|
|
|
|
* - { get: { type: "object", class: "Function" },
|
|
|
|
* set: { type: "undefined" } }
|
|
|
|
* @return Property
|
|
|
|
* The newly created Property instance, null if it already exists.
|
|
|
|
*/
|
2012-11-03 16:01:05 -07:00
|
|
|
addProperty: function V_addProperty(aName = "", aDescriptor = {}) {
|
2012-10-26 10:10:17 -07:00
|
|
|
if (this._store.has(aName)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let property = new Property(this, aName, aDescriptor);
|
|
|
|
this._store.set(aName, property);
|
|
|
|
this._variablesView._currHierarchy.set(property._absoluteName, property);
|
2012-11-03 16:01:05 -07:00
|
|
|
property.header = !!aName;
|
2012-10-26 10:10:17 -07:00
|
|
|
return property;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds properties for this variable.
|
|
|
|
*
|
|
|
|
* @param object aProperties
|
|
|
|
* An object containing some { name: descriptor } data properties,
|
|
|
|
* specifying the value and/or type & class of the variable,
|
|
|
|
* or 'get' & 'set' accessor properties. If the type is implicit,
|
|
|
|
* it will be inferred from the value.
|
|
|
|
* e.g. - { someProp0: { value: 42 },
|
|
|
|
* someProp1: { value: true },
|
|
|
|
* someProp2: { value: "nasu" },
|
|
|
|
* someProp3: { value: { type: "undefined" } },
|
|
|
|
* someProp4: { value: { type: "null" } },
|
|
|
|
* someProp5: { valu§e: { type: "object", class: "Object" } },
|
|
|
|
* someProp6: { get: { type: "object", class: "Function" },
|
|
|
|
* set: { type: "undefined" } }
|
|
|
|
*/
|
|
|
|
addProperties: function V_addProperties(aProperties) {
|
|
|
|
// Sort all of the properties before adding them.
|
|
|
|
let sortedPropertyNames = Object.keys(aProperties).sort();
|
|
|
|
|
|
|
|
for (let name of sortedPropertyNames) {
|
|
|
|
this.addProperty(name, aProperties[name]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Populates this variable to contain all the properties of an object.
|
|
|
|
*
|
|
|
|
* @param object aObject
|
|
|
|
* The raw object you want to display.
|
|
|
|
*/
|
|
|
|
populate: function V_populate(aObject) {
|
|
|
|
// Retrieve the properties only once.
|
|
|
|
if (this.fetched) {
|
|
|
|
return;
|
|
|
|
}
|
2012-11-15 22:34:13 -08:00
|
|
|
this.fetched = true;
|
2012-11-03 16:01:05 -07:00
|
|
|
|
|
|
|
// Sort all of the properties before adding them.
|
|
|
|
let sortedPropertyNames = Object.getOwnPropertyNames(aObject).sort();
|
|
|
|
let prototype = Object.getPrototypeOf(aObject);
|
|
|
|
|
|
|
|
// Add all the variable properties.
|
|
|
|
for (let name of sortedPropertyNames) {
|
|
|
|
let descriptor = Object.getOwnPropertyDescriptor(aObject, name);
|
|
|
|
if (descriptor.get || descriptor.set) {
|
|
|
|
this._addRawNonValueProperty(name, descriptor);
|
|
|
|
} else {
|
|
|
|
this._addRawValueProperty(name, descriptor, aObject[name]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add the variable's __proto__.
|
|
|
|
if (prototype) {
|
|
|
|
this._addRawValueProperty("__proto__", {}, prototype);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a property for this variable based on a raw value descriptor.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The property's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* Specifies the exact property descriptor as returned by a call to
|
|
|
|
* Object.getOwnPropertyDescriptor.
|
|
|
|
* @param object aValue
|
|
|
|
* The raw property value you want to display.
|
|
|
|
*/
|
|
|
|
_addRawValueProperty: function V__addRawValueProperty(aName, aDescriptor, aValue) {
|
|
|
|
let descriptor = Object.create(aDescriptor);
|
|
|
|
descriptor.value = VariablesView.getGrip(aValue);
|
|
|
|
|
|
|
|
let propertyItem = this.addProperty(aName, descriptor);
|
|
|
|
|
|
|
|
// Add an 'onexpand' callback for the property, lazily handling
|
|
|
|
// the addition of new child properties.
|
|
|
|
if (!VariablesView.isPrimitive(descriptor)) {
|
|
|
|
propertyItem.onexpand = this.populate.bind(propertyItem, aValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
return propertyItem;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a property for this variable based on a getter/setter descriptor.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The property's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* Specifies the exact property descriptor as returned by a call to
|
|
|
|
* Object.getOwnPropertyDescriptor.
|
|
|
|
*/
|
|
|
|
_addRawNonValueProperty: function V__addRawNonValueProperty(aName, aDescriptor) {
|
|
|
|
let descriptor = Object.create(aDescriptor);
|
|
|
|
descriptor.get = VariablesView.getGrip(aDescriptor.get);
|
|
|
|
descriptor.set = VariablesView.getGrip(aDescriptor.set);
|
|
|
|
|
|
|
|
let propertyItem = this.addProperty(aName, descriptor);
|
|
|
|
return propertyItem;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns this variable's value from the descriptor if available,
|
|
|
|
*/
|
|
|
|
get value() this._initialDescriptor.value,
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Returns this variable's getter from the descriptor if available,
|
|
|
|
*/
|
|
|
|
get getter() this._initialDescriptor.get,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns this variable's getter from the descriptor if available,
|
|
|
|
*/
|
|
|
|
get setter() this._initialDescriptor.set,
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Sets the specific grip for this variable.
|
|
|
|
* The grip should contain the value or the type & class, as defined in the
|
|
|
|
* remote debugger protocol. For convenience, undefined and null are
|
|
|
|
* both considered types.
|
|
|
|
*
|
|
|
|
* @param any aGrip
|
|
|
|
* Specifies the value and/or type & class of the variable.
|
|
|
|
* e.g. - 42
|
|
|
|
* - true
|
|
|
|
* - "nasu"
|
|
|
|
* - { type: "undefined" }
|
|
|
|
* - { type: "null" }
|
|
|
|
* - { type: "object", class: "Object" }
|
|
|
|
*/
|
|
|
|
_setGrip: function V__setGrip(aGrip) {
|
|
|
|
if (aGrip === undefined) {
|
|
|
|
aGrip = { type: "undefined" };
|
|
|
|
}
|
|
|
|
if (aGrip === null) {
|
|
|
|
aGrip = { type: "null" };
|
|
|
|
}
|
|
|
|
this._applyGrip(aGrip);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies the necessary text content and class name to a value node based
|
|
|
|
* on a grip.
|
|
|
|
*
|
|
|
|
* @param any aGrip
|
|
|
|
* @see Variable._setGrip
|
|
|
|
*/
|
|
|
|
_applyGrip: function V__applyGrip(aGrip) {
|
|
|
|
let prevGrip = this._valueGrip;
|
|
|
|
if (prevGrip) {
|
|
|
|
this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
|
|
|
|
}
|
|
|
|
this._valueGrip = aGrip;
|
|
|
|
this._valueString = VariablesView.getString(aGrip);
|
|
|
|
this._valueClassName = VariablesView.getClass(aGrip);
|
|
|
|
|
|
|
|
this._valueLabel.classList.add(this._valueClassName);
|
|
|
|
this._valueLabel.setAttribute("value", this._valueString);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes this variable's id, view and binds event listeners.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The variable's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The variable's descriptor.
|
|
|
|
*/
|
|
|
|
_init: function V__init(aName, aDescriptor) {
|
|
|
|
this._idString = generateId(this._nameString = aName);
|
|
|
|
this._createScope(aName, "variable");
|
|
|
|
this._displayVariable(aDescriptor);
|
2012-11-15 22:34:13 -08:00
|
|
|
this._prepareTooltip();
|
2012-10-26 10:10:17 -07:00
|
|
|
this._setAttributes(aName, aDescriptor);
|
|
|
|
this._addEventListeners();
|
|
|
|
|
|
|
|
if (aDescriptor.enumerable || aName == "this" || aName == "<exception>") {
|
|
|
|
this.ownerView._enum.appendChild(this._target);
|
|
|
|
} else {
|
|
|
|
this.ownerView._nonenum.appendChild(this._target);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates the necessary nodes for this variable.
|
|
|
|
*
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The property's descriptor.
|
|
|
|
*/
|
|
|
|
_displayVariable: function V__displayVariable(aDescriptor) {
|
|
|
|
let document = this.document;
|
|
|
|
|
|
|
|
let separatorLabel = this._separatorLabel = document.createElement("label");
|
|
|
|
separatorLabel.className = "plain";
|
2012-11-17 11:41:04 -08:00
|
|
|
separatorLabel.setAttribute("value", this.ownerView.separator);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
let valueLabel = this._valueLabel = document.createElement("label");
|
|
|
|
valueLabel.className = "value plain";
|
|
|
|
|
|
|
|
this._title.appendChild(separatorLabel);
|
|
|
|
this._title.appendChild(valueLabel);
|
|
|
|
|
2012-11-17 11:41:04 -08:00
|
|
|
let isPrimitive = VariablesView.isPrimitive(aDescriptor);
|
|
|
|
let isUndefined = VariablesView.isUndefined(aDescriptor);
|
|
|
|
|
|
|
|
if (isPrimitive || isUndefined) {
|
2012-10-26 10:10:17 -07:00
|
|
|
this.hideArrow();
|
|
|
|
}
|
2012-11-17 11:41:04 -08:00
|
|
|
if (!isUndefined && (aDescriptor.get || aDescriptor.set)) {
|
2012-11-03 16:01:05 -07:00
|
|
|
this.addProperty("get", { value: aDescriptor.get });
|
|
|
|
this.addProperty("set", { value: aDescriptor.set });
|
2012-11-15 22:34:13 -08:00
|
|
|
this.expand();
|
2012-10-26 10:10:17 -07:00
|
|
|
separatorLabel.hidden = true;
|
|
|
|
valueLabel.hidden = true;
|
|
|
|
}
|
2012-11-27 08:19:23 -08:00
|
|
|
if (this.ownerView.allowDeletion) {
|
|
|
|
let closeNode = this._closeNode = document.createElement("toolbarbutton");
|
|
|
|
closeNode.className = "dbg-variables-delete plain devtools-closebutton";
|
|
|
|
closeNode.addEventListener("click", this._onClose, false);
|
|
|
|
this._title.appendChild(closeNode);
|
|
|
|
}
|
2012-12-01 01:21:23 -08:00
|
|
|
if (this.ownerView.contextMenu) {
|
|
|
|
this._title.setAttribute("context", this.ownerView.contextMenu);
|
|
|
|
}
|
2012-10-26 10:10:17 -07:00
|
|
|
},
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
/**
|
|
|
|
* Prepares a tooltip for this variable.
|
|
|
|
*/
|
|
|
|
_prepareTooltip: function V__prepareTooltip() {
|
|
|
|
this._target.addEventListener("mouseover", this._displayTooltip, false);
|
|
|
|
},
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Creates a tooltip for this variable.
|
|
|
|
*/
|
|
|
|
_displayTooltip: function V__displayTooltip() {
|
2012-11-15 22:34:13 -08:00
|
|
|
this._target.removeEventListener("mouseover", this._displayTooltip, false);
|
2012-10-26 10:10:17 -07:00
|
|
|
let document = this.document;
|
|
|
|
|
2012-12-10 23:36:50 -08:00
|
|
|
if (this.ownerView.showDescriptorTooltip) {
|
|
|
|
let tooltip = document.createElement("tooltip");
|
|
|
|
tooltip.id = "tooltip-" + this.id;
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-12-10 23:36:50 -08:00
|
|
|
let configurableLabel = document.createElement("label");
|
|
|
|
configurableLabel.setAttribute("value", "configurable");
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-12-10 23:36:50 -08:00
|
|
|
let enumerableLabel = document.createElement("label");
|
|
|
|
enumerableLabel.setAttribute("value", "enumerable");
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-12-10 23:36:50 -08:00
|
|
|
let writableLabel = document.createElement("label");
|
|
|
|
writableLabel.setAttribute("value", "writable");
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-12-10 23:36:50 -08:00
|
|
|
tooltip.setAttribute("orient", "horizontal")
|
|
|
|
tooltip.appendChild(configurableLabel);
|
|
|
|
tooltip.appendChild(enumerableLabel);
|
|
|
|
tooltip.appendChild(writableLabel);
|
2012-11-27 08:19:23 -08:00
|
|
|
|
2012-12-10 23:36:50 -08:00
|
|
|
this._target.appendChild(tooltip);
|
|
|
|
this._target.setAttribute("tooltip", tooltip.id);
|
|
|
|
}
|
2012-11-27 08:19:23 -08:00
|
|
|
if (this.ownerView.allowNameInput) {
|
|
|
|
this._name.setAttribute("tooltiptext", L10N.getStr("variablesEditableNameTooltip"));
|
|
|
|
}
|
|
|
|
if (this.ownerView.allowValueInput) {
|
|
|
|
this._valueLabel.setAttribute("tooltiptext", L10N.getStr("variablesEditableValueTooltip"));
|
|
|
|
}
|
|
|
|
if (this.ownerView.allowDeletion) {
|
|
|
|
this._closeNode.setAttribute("tooltiptext", L10N.getStr("variablesCloseButtonTooltip"));
|
|
|
|
}
|
2012-10-26 10:10:17 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a variable's configurable, enumerable and writable attributes,
|
|
|
|
* and specifies if it's a 'this', '<exception>' or '__proto__' reference.
|
|
|
|
*
|
|
|
|
* @param object aName
|
|
|
|
* The varialbe name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The variable's descriptor.
|
|
|
|
*/
|
|
|
|
_setAttributes: function V__setAttributes(aName, aDescriptor) {
|
|
|
|
if (aDescriptor) {
|
|
|
|
if (!aDescriptor.configurable) {
|
|
|
|
this._target.setAttribute("non-configurable", "");
|
|
|
|
}
|
|
|
|
if (!aDescriptor.enumerable) {
|
|
|
|
this._target.setAttribute("non-enumerable", "");
|
|
|
|
}
|
|
|
|
if (!aDescriptor.writable) {
|
|
|
|
this._target.setAttribute("non-writable", "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (aName == "this") {
|
|
|
|
this._target.setAttribute("self", "");
|
|
|
|
}
|
|
|
|
if (aName == "<exception>") {
|
|
|
|
this._target.setAttribute("exception", "");
|
|
|
|
}
|
|
|
|
if (aName == "__proto__") {
|
|
|
|
this._target.setAttribute("proto", "");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the necessary event listeners for this variable.
|
|
|
|
*/
|
|
|
|
_addEventListeners: function V__addEventListeners() {
|
|
|
|
this._arrow.addEventListener("mousedown", this.toggle, false);
|
|
|
|
this._name.addEventListener("mousedown", this.toggle, false);
|
2012-11-27 08:19:23 -08:00
|
|
|
this._name.addEventListener("dblclick", this._activateNameInput, false);
|
|
|
|
this._valueLabel.addEventListener("click", this._activateValueInput, false);
|
2012-10-26 10:10:17 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-11-27 08:19:23 -08:00
|
|
|
* The click listener for the close button.
|
2012-10-26 10:10:17 -07:00
|
|
|
*/
|
2012-11-27 08:19:23 -08:00
|
|
|
_onClose: function V__onClose() {
|
|
|
|
this.hide();
|
|
|
|
|
|
|
|
if (this.delete) {
|
|
|
|
this.delete(this);
|
2012-10-26 10:10:17 -07:00
|
|
|
}
|
2012-11-27 08:19:23 -08:00
|
|
|
},
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-11-27 08:19:23 -08:00
|
|
|
/**
|
|
|
|
* Creates a textbox node in place of a label.
|
|
|
|
*
|
|
|
|
* @param nsIDOMNode aLabel
|
|
|
|
* The label to be replaced with a textbox.
|
|
|
|
* @param string aClassName
|
|
|
|
* The class to be applied to the textbox.
|
|
|
|
* @param object aCallbacks
|
|
|
|
* An object containing the onKeypress and onBlur callbacks.
|
|
|
|
*/
|
|
|
|
_activateInput: function V__activateInput(aLabel, aClassName, aCallbacks) {
|
|
|
|
let initialString = aLabel.getAttribute("value");
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
// Create a texbox input element which will be shown in the current
|
2012-11-27 08:19:23 -08:00
|
|
|
// element's specified label location.
|
2012-10-26 10:10:17 -07:00
|
|
|
let input = this.document.createElement("textbox");
|
|
|
|
input.setAttribute("value", initialString);
|
2012-11-27 08:19:23 -08:00
|
|
|
input.className = "plain " + aClassName;
|
2012-11-15 22:34:13 -08:00
|
|
|
input.width = this._target.clientWidth;
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-11-27 08:19:23 -08:00
|
|
|
aLabel.parentNode.replaceChild(input, aLabel);
|
2012-10-26 10:10:17 -07:00
|
|
|
input.select();
|
|
|
|
|
|
|
|
// When the value is a string (displayed as "value"), then we probably want
|
|
|
|
// to change it to another string in the textbox, so to avoid typing the ""
|
|
|
|
// again, tackle with the selection bounds just a bit.
|
2012-11-27 08:19:23 -08:00
|
|
|
if (aLabel.getAttribute("value").match(/^"[^"]*"$/)) {
|
2012-10-26 10:10:17 -07:00
|
|
|
input.selectionEnd--;
|
|
|
|
input.selectionStart++;
|
|
|
|
}
|
|
|
|
|
2012-11-27 08:19:23 -08:00
|
|
|
input.addEventListener("keypress", aCallbacks.onKeypress, false);
|
|
|
|
input.addEventListener("blur", aCallbacks.onBlur, false);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
this._prevExpandable = this.twisty;
|
|
|
|
this._prevExpanded = this.expanded;
|
|
|
|
this.collapse();
|
|
|
|
this.hideArrow();
|
|
|
|
this._locked = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-11-27 08:19:23 -08:00
|
|
|
* Removes the textbox node in place of a label.
|
|
|
|
*
|
|
|
|
* @param nsIDOMNode aLabel
|
|
|
|
* The label which was replaced with a textbox.
|
|
|
|
* @param object aCallbacks
|
|
|
|
* An object containing the onKeypress and onBlur callbacks.
|
2012-10-26 10:10:17 -07:00
|
|
|
*/
|
2012-11-27 08:19:23 -08:00
|
|
|
_deactivateInput: function V__deactivateInput(aLabel, aInput, aCallbacks) {
|
|
|
|
aInput.parentNode.replaceChild(aLabel, aInput);
|
|
|
|
aInput.removeEventListener("keypress", aCallbacks.onKeypress, false);
|
|
|
|
aInput.removeEventListener("blur", aCallbacks.onBlur, false);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
this._locked = false;
|
|
|
|
this.twisty = this._prevExpandable;
|
|
|
|
this.expanded = this._prevExpanded;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-11-27 08:19:23 -08:00
|
|
|
* Makes this variable's name editable.
|
|
|
|
*/
|
2012-12-01 01:21:23 -08:00
|
|
|
_activateNameInput: function V__activateNameInput(e) {
|
|
|
|
if (e && e.button != 0) {
|
|
|
|
// Only allow left-click to trigger this event.
|
|
|
|
return;
|
|
|
|
}
|
2012-11-27 08:19:23 -08:00
|
|
|
if (!this.ownerView.allowNameInput || !this.switch) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._activateInput(this._name, "element-name-input", {
|
|
|
|
onKeypress: this._onNameInputKeyPress,
|
|
|
|
onBlur: this._deactivateNameInput
|
|
|
|
});
|
|
|
|
this._separatorLabel.hidden = true;
|
|
|
|
this._valueLabel.hidden = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deactivates this variable's editable name mode.
|
|
|
|
*/
|
|
|
|
_deactivateNameInput: function V__deactivateNameInput(e) {
|
|
|
|
this._deactivateInput(this._name, e.target, {
|
|
|
|
onKeypress: this._onNameInputKeyPress,
|
|
|
|
onBlur: this._deactivateNameInput
|
|
|
|
});
|
|
|
|
this._separatorLabel.hidden = false;
|
|
|
|
this._valueLabel.hidden = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes this variable's value editable.
|
|
|
|
*/
|
2012-12-01 01:21:23 -08:00
|
|
|
_activateValueInput: function V__activateValueInput(e) {
|
|
|
|
if (e && e.button != 0) {
|
|
|
|
// Only allow left-click to trigger this event.
|
|
|
|
return;
|
|
|
|
}
|
2012-11-27 08:19:23 -08:00
|
|
|
if (!this.ownerView.allowValueInput || !this.eval) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._activateInput(this._valueLabel, "element-value-input", {
|
|
|
|
onKeypress: this._onValueInputKeyPress,
|
|
|
|
onBlur: this._deactivateValueInput
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deactivates this variable's editable value mode.
|
2012-10-26 10:10:17 -07:00
|
|
|
*/
|
2012-11-27 08:19:23 -08:00
|
|
|
_deactivateValueInput: function V__deactivateValueInput(e) {
|
|
|
|
this._deactivateInput(this._valueLabel, e.target, {
|
|
|
|
onKeypress: this._onValueInputKeyPress,
|
|
|
|
onBlur: this._deactivateValueInput
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disables this variable prior to a new name switch or value evaluation.
|
|
|
|
*/
|
|
|
|
_disable: function V__disable() {
|
|
|
|
this.twisty = false;
|
|
|
|
this._separatorLabel.hidden = true;
|
|
|
|
this._valueLabel.hidden = true;
|
|
|
|
this._enum.hidden = true;
|
|
|
|
this._nonenum.hidden = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deactivates this variable's editable mode and callbacks the new name.
|
|
|
|
*/
|
|
|
|
_saveNameInput: function V__saveNameInput(e) {
|
2012-10-26 10:10:17 -07:00
|
|
|
let input = e.target;
|
2012-11-27 08:19:23 -08:00
|
|
|
let initialString = this._name.getAttribute("value");
|
|
|
|
let currentString = input.value.trim();
|
|
|
|
this._deactivateNameInput(e);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
2012-11-27 08:19:23 -08:00
|
|
|
if (initialString != currentString) {
|
|
|
|
this._disable();
|
|
|
|
this._name.value = currentString;
|
|
|
|
this.switch(this, currentString);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deactivates this variable's editable mode and evaluates the new value.
|
|
|
|
*/
|
|
|
|
_saveValueInput: function V__saveValueInput(e) {
|
|
|
|
let input = e.target;
|
|
|
|
let initialString = this._valueLabel.getAttribute("value");
|
|
|
|
let currentString = input.value.trim();
|
|
|
|
this._deactivateValueInput(e);
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
if (initialString != currentString) {
|
2012-11-27 08:19:23 -08:00
|
|
|
this._disable();
|
|
|
|
this.eval(this._symbolicName + "=" + currentString);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The key press listener for this variable's editable name textbox.
|
|
|
|
*/
|
|
|
|
_onNameInputKeyPress: function V__onNameInputKeyPress(e) {
|
|
|
|
switch(e.keyCode) {
|
|
|
|
case e.DOM_VK_RETURN:
|
|
|
|
case e.DOM_VK_ENTER:
|
|
|
|
this._saveNameInput(e);
|
|
|
|
return;
|
|
|
|
case e.DOM_VK_ESCAPE:
|
|
|
|
this._deactivateNameInput(e);
|
|
|
|
return;
|
2012-10-26 10:10:17 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-11-27 08:19:23 -08:00
|
|
|
* The key press listener for this variable's editable value textbox.
|
2012-10-26 10:10:17 -07:00
|
|
|
*/
|
2012-11-27 08:19:23 -08:00
|
|
|
_onValueInputKeyPress: function V__onValueInputKeyPress(e) {
|
2012-10-26 10:10:17 -07:00
|
|
|
switch(e.keyCode) {
|
|
|
|
case e.DOM_VK_RETURN:
|
|
|
|
case e.DOM_VK_ENTER:
|
2012-11-27 08:19:23 -08:00
|
|
|
this._saveValueInput(e);
|
2012-10-26 10:10:17 -07:00
|
|
|
return;
|
|
|
|
case e.DOM_VK_ESCAPE:
|
2012-11-27 08:19:23 -08:00
|
|
|
this._deactivateValueInput(e);
|
2012-10-26 10:10:17 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_symbolicName: "",
|
|
|
|
_absoluteName: "",
|
|
|
|
_initialDescriptor: null,
|
|
|
|
_separatorLabel: null,
|
|
|
|
_valueLabel: null,
|
2012-11-27 08:19:23 -08:00
|
|
|
_closeNode: null,
|
2012-10-26 10:10:17 -07:00
|
|
|
_tooltip: null,
|
|
|
|
_valueGrip: null,
|
|
|
|
_valueString: "",
|
|
|
|
_valueClassName: "",
|
|
|
|
_prevExpandable: false,
|
|
|
|
_prevExpanded: false
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Property is a Variable holding additional child Property instances.
|
|
|
|
* Iterable via "for (let [name, property] in instance) { }".
|
|
|
|
*
|
|
|
|
* @param Variable aVar
|
|
|
|
* The variable to contain this property.
|
|
|
|
* @param string aName
|
|
|
|
* The property's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The property's descriptor.
|
|
|
|
*/
|
|
|
|
function Property(aVar, aName, aDescriptor) {
|
|
|
|
Variable.call(this, aVar, aName, aDescriptor);
|
|
|
|
this._symbolicName = aVar._symbolicName + "[\"" + aName + "\"]";
|
|
|
|
this._absoluteName = aVar._absoluteName + "." + aName;
|
|
|
|
this._initialDescriptor = aDescriptor;
|
|
|
|
}
|
|
|
|
|
|
|
|
create({ constructor: Property, proto: Variable.prototype }, {
|
|
|
|
/**
|
|
|
|
* Initializes this property's id, view and binds event listeners.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* The property's name.
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The property's descriptor.
|
|
|
|
*/
|
|
|
|
_init: function P__init(aName, aDescriptor) {
|
|
|
|
this._idString = generateId(this._nameString = aName);
|
|
|
|
this._createScope(aName, "property");
|
|
|
|
this._displayVariable(aDescriptor);
|
2012-11-15 22:34:13 -08:00
|
|
|
this._prepareTooltip();
|
2012-10-26 10:10:17 -07:00
|
|
|
this._setAttributes(aName, aDescriptor);
|
|
|
|
this._addEventListeners();
|
|
|
|
|
|
|
|
if (aDescriptor.enumerable) {
|
|
|
|
this.ownerView._enum.appendChild(this._target);
|
|
|
|
} else {
|
|
|
|
this.ownerView._nonenum.appendChild(this._target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A generator-iterator over the VariablesView, Scopes, Variables and Properties.
|
|
|
|
*/
|
|
|
|
VariablesView.prototype.__iterator__ =
|
|
|
|
Scope.prototype.__iterator__ =
|
|
|
|
Variable.prototype.__iterator__ =
|
|
|
|
Property.prototype.__iterator__ = function VV_iterator() {
|
|
|
|
for (let item of this._store) {
|
|
|
|
yield item;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start recording a hierarchy of any added scopes, variables or properties.
|
|
|
|
* @see VariablesView.commitHierarchy
|
|
|
|
*/
|
|
|
|
VariablesView.prototype.createHierarchy = function VV_createHierarchy() {
|
|
|
|
this._prevHierarchy = this._currHierarchy;
|
|
|
|
this._currHierarchy = new Map();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Briefly flash the variables that changed between the previous and current
|
2012-10-26 13:28:54 -07:00
|
|
|
* scope/variable/property hierarchies and reopen previously expanded nodes.
|
2012-10-26 10:10:17 -07:00
|
|
|
*/
|
|
|
|
VariablesView.prototype.commitHierarchy = function VV_commitHierarchy() {
|
|
|
|
let prevHierarchy = this._prevHierarchy;
|
|
|
|
let currHierarchy = this._currHierarchy;
|
|
|
|
|
|
|
|
for (let [absoluteName, currVariable] of currHierarchy) {
|
|
|
|
// Ignore variables which were already commmitted.
|
|
|
|
if (currVariable._committed) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-11-15 22:34:13 -08:00
|
|
|
// Avoid performing expensive operations.
|
|
|
|
if (this.commitHierarchyIgnoredItems[currVariable._nameString]) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-10-26 10:10:17 -07:00
|
|
|
|
|
|
|
// Try to get the previous instance of the inspected variable to
|
|
|
|
// determine the difference in state.
|
|
|
|
let prevVariable = prevHierarchy.get(absoluteName);
|
2012-11-15 22:34:13 -08:00
|
|
|
let expanded = false;
|
2012-10-26 10:10:17 -07:00
|
|
|
let changed = false;
|
|
|
|
|
|
|
|
// If the inspected variable existed in a previous hierarchy, check if
|
2012-11-15 22:34:13 -08:00
|
|
|
// the displayed value (a representation of the grip) has changed and if
|
|
|
|
// it was previously expanded.
|
2012-10-26 10:10:17 -07:00
|
|
|
if (prevVariable) {
|
2012-11-15 22:34:13 -08:00
|
|
|
expanded = prevVariable._isExpanded;
|
|
|
|
changed = prevVariable._valueString != currVariable._valueString;
|
2012-10-26 10:10:17 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure this variable is not handled in ulteror commits for the
|
|
|
|
// same hierarchy.
|
|
|
|
currVariable._committed = true;
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
// Re-expand the variable if not previously collapsed.
|
|
|
|
if (expanded) {
|
|
|
|
currVariable._wasToggled = prevVariable._wasToggled;
|
|
|
|
currVariable.expand();
|
|
|
|
}
|
2012-10-26 10:10:17 -07:00
|
|
|
// This variable was either not changed or removed, no need to continue.
|
|
|
|
if (!changed) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply an attribute determining the flash type and duration.
|
|
|
|
// Dispatch this action after all the nodes have been drawn, so that
|
|
|
|
// the transition efects can take place.
|
2012-10-26 13:28:54 -07:00
|
|
|
this.window.setTimeout(function(aTarget) {
|
2012-10-26 10:10:17 -07:00
|
|
|
aTarget.addEventListener("transitionend", function onEvent() {
|
|
|
|
aTarget.removeEventListener("transitionend", onEvent, false);
|
|
|
|
aTarget.removeAttribute("changed");
|
|
|
|
}, false);
|
2013-01-03 23:37:18 -08:00
|
|
|
aTarget.setAttribute("changed", "");
|
2012-10-26 13:28:54 -07:00
|
|
|
}.bind(this, currVariable.target), LAZY_EMPTY_DELAY + 1);
|
2012-10-26 10:10:17 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-11-15 22:34:13 -08:00
|
|
|
// Some variables are likely to contain a very large number of properties.
|
|
|
|
// It would be a bad idea to re-expand them or perform expensive operations.
|
|
|
|
VariablesView.prototype.commitHierarchyIgnoredItems = Object.create(null, {
|
|
|
|
"window": { value: true },
|
|
|
|
"this": { value: true }
|
|
|
|
});
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Returns true if the descriptor represents an undefined, null or
|
|
|
|
* primitive value.
|
|
|
|
*
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The variable's descriptor.
|
|
|
|
*/
|
|
|
|
VariablesView.isPrimitive = function VV_isPrimitive(aDescriptor) {
|
|
|
|
if (!aDescriptor || typeof aDescriptor != "object") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For accessor property descriptors, the getter and setter need to be
|
|
|
|
// contained in 'get' and 'set' properties.
|
|
|
|
let getter = aDescriptor.get;
|
|
|
|
let setter = aDescriptor.set;
|
|
|
|
if (getter || setter) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// As described in the remote debugger protocol, the value grip
|
|
|
|
// must be contained in a 'value' property.
|
|
|
|
let grip = aDescriptor.value;
|
|
|
|
if (!grip || typeof grip != "object") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For convenience, undefined and null are both considered types.
|
|
|
|
let type = grip.type;
|
2012-11-15 22:42:52 -08:00
|
|
|
if (type == "undefined" || type == "null") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2012-11-17 11:41:04 -08:00
|
|
|
/**
|
|
|
|
* Returns true if the descriptor represents an undefined value.
|
|
|
|
*
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The variable's descriptor.
|
|
|
|
*/
|
|
|
|
VariablesView.isUndefined = function VV_isUndefined(aDescriptor) {
|
|
|
|
// For accessor property descriptors, the getter and setter need to be
|
|
|
|
// contained in 'get' and 'set' properties.
|
|
|
|
let getter = aDescriptor.get;
|
|
|
|
let setter = aDescriptor.set;
|
|
|
|
if (typeof getter == "object" && getter.type == "undefined" &&
|
|
|
|
typeof setter == "object" && setter.type == "undefined") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// As described in the remote debugger protocol, the value grip
|
|
|
|
// must be contained in a 'value' property.
|
|
|
|
// For convenience, undefined is considered a type.
|
|
|
|
let grip = aDescriptor.value;
|
|
|
|
if (grip && grip.type == "undefined") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2012-11-15 22:42:52 -08:00
|
|
|
/**
|
|
|
|
* Returns true if the descriptor represents a falsy value.
|
|
|
|
*
|
|
|
|
* @param object aDescriptor
|
|
|
|
* The variable's descriptor.
|
|
|
|
*/
|
|
|
|
VariablesView.isFalsy = function VV_isFalsy(aDescriptor) {
|
|
|
|
if (!aDescriptor || typeof aDescriptor != "object") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// As described in the remote debugger protocol, the value grip
|
|
|
|
// must be contained in a 'value' property.
|
|
|
|
let grip = aDescriptor.value;
|
|
|
|
if (typeof grip != "object") {
|
|
|
|
return !grip;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For convenience, undefined and null are both considered types.
|
|
|
|
let type = grip.type;
|
|
|
|
if (type == "undefined" || type == "null") {
|
2012-10-26 10:10:17 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2012-11-03 16:01:05 -07:00
|
|
|
/**
|
|
|
|
* Returns a standard grip for a value.
|
|
|
|
*
|
|
|
|
* @param any aValue
|
|
|
|
* The raw value to get a grip for.
|
|
|
|
* @return any
|
|
|
|
* The value's grip.
|
|
|
|
*/
|
|
|
|
VariablesView.getGrip = function VV_getGrip(aValue) {
|
|
|
|
if (aValue === undefined) {
|
|
|
|
return { type: "undefined" };
|
|
|
|
}
|
|
|
|
if (aValue === null) {
|
|
|
|
return { type: "null" };
|
|
|
|
}
|
|
|
|
if (typeof aValue == "object" || typeof aValue == "function") {
|
|
|
|
if (aValue.constructor) {
|
|
|
|
return { type: "object", class: aValue.constructor.name };
|
|
|
|
} else {
|
|
|
|
return { type: "object", class: "Object" };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return aValue;
|
|
|
|
};
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* Returns a custom formatted property string for a grip.
|
|
|
|
*
|
|
|
|
* @param any aGrip
|
|
|
|
* @see Variable._setGrip
|
|
|
|
* @param boolean aConciseFlag
|
|
|
|
* Return a concisely formatted property string.
|
|
|
|
* @return string
|
|
|
|
* The formatted property string.
|
|
|
|
*/
|
|
|
|
VariablesView.getString = function VV_getString(aGrip, aConciseFlag) {
|
|
|
|
if (aGrip && typeof aGrip == "object") {
|
|
|
|
switch (aGrip.type) {
|
|
|
|
case "undefined":
|
|
|
|
return "undefined";
|
|
|
|
case "null":
|
|
|
|
return "null";
|
|
|
|
default:
|
|
|
|
if (!aConciseFlag) {
|
|
|
|
return "[" + aGrip.type + " " + aGrip.class + "]";
|
|
|
|
} else {
|
|
|
|
return aGrip.class;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (typeof aGrip) {
|
|
|
|
case "string":
|
|
|
|
return "\"" + aGrip + "\"";
|
|
|
|
case "boolean":
|
|
|
|
return aGrip ? "true" : "false";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return aGrip + "";
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a custom class style for a grip.
|
|
|
|
*
|
|
|
|
* @param any aGrip
|
|
|
|
* @see Variable._setGrip
|
|
|
|
* @return string
|
|
|
|
* The custom class style.
|
|
|
|
*/
|
|
|
|
VariablesView.getClass = function VV_getClass(aGrip) {
|
|
|
|
if (aGrip && typeof aGrip == "object") {
|
|
|
|
switch (aGrip.type) {
|
|
|
|
case "undefined":
|
|
|
|
return "token-undefined";
|
|
|
|
case "null":
|
|
|
|
return "token-null";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (typeof aGrip) {
|
|
|
|
case "string":
|
|
|
|
return "token-string";
|
|
|
|
case "boolean":
|
|
|
|
return "token-boolean";
|
|
|
|
case "number":
|
|
|
|
return "token-number";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "token-other";
|
|
|
|
};
|
|
|
|
|
2012-11-17 11:41:04 -08:00
|
|
|
/**
|
|
|
|
* Localization convenience methods.
|
|
|
|
*/
|
|
|
|
let L10N = {
|
|
|
|
/**
|
|
|
|
* L10N shortcut function.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
getStr: function L10N_getStr(aName) {
|
|
|
|
return this.stringBundle.GetStringFromName(aName);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
|
|
|
|
return Services.strings.createBundle(DBG_STRINGS_URI);
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The separator label between the variables or properties name and value.
|
2012-11-27 08:19:23 -08:00
|
|
|
* This property applies non-recursively to the current scope.
|
2012-11-17 11:41:04 -08:00
|
|
|
*/
|
|
|
|
Scope.prototype.separator = L10N.getStr("variablesSeparatorLabel");
|
|
|
|
|
2012-10-26 10:10:17 -07:00
|
|
|
/**
|
|
|
|
* A monotonically-increasing counter, that guarantees the uniqueness of scope,
|
|
|
|
* variables and properties ids.
|
|
|
|
*
|
|
|
|
* @param string aName
|
|
|
|
* An optional string to prefix the id with.
|
|
|
|
* @return number
|
|
|
|
* A unique id.
|
|
|
|
*/
|
|
|
|
let generateId = (function() {
|
|
|
|
let count = 0;
|
2012-11-17 11:41:04 -08:00
|
|
|
return function VV_generateId(aName = "") {
|
2012-10-26 10:10:17 -07:00
|
|
|
return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count);
|
2012-11-17 11:41:04 -08:00
|
|
|
};
|
2012-10-26 10:10:17 -07:00
|
|
|
})();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sugar for prototypal inheritance using Object.create.
|
|
|
|
* Creates a new object with the specified prototype object and properties.
|
|
|
|
*
|
|
|
|
* @param object target
|
|
|
|
* @param object properties
|
|
|
|
*/
|
|
|
|
function create({ constructor, proto }, properties = {}) {
|
|
|
|
let propertiesObject = {
|
|
|
|
constructor: { value: constructor }
|
|
|
|
};
|
|
|
|
for (let name in properties) {
|
|
|
|
propertiesObject[name] = Object.getOwnPropertyDescriptor(properties, name);
|
|
|
|
}
|
|
|
|
constructor.prototype = Object.create(proto, propertiesObject);
|
|
|
|
}
|