copy from devtools/client/webconsole/jsterm.js
copy to devtools/client/webconsole/components/JSTerm.js
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -1,90 +1,214 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const {Utils: WebConsoleUtils} =
- require("devtools/client/webconsole/utils");
+const {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
const defer = require("devtools/shared/defer");
const Debugger = require("Debugger");
const Services = require("Services");
const {KeyCodes} = require("devtools/client/shared/keycodes");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
loader.lazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
-loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/framework/sidebar", true);
-loader.lazyRequireGetter(this, "Messages", "devtools/client/webconsole/old/console-output", true);
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
-loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/environment-client");
-loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
-loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
-loader.lazyImporter(this, "VariablesViewController", "resource://devtools/client/shared/widgets/VariablesViewController.jsm");
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "NotificationBox", "devtools/client/shared/components/NotificationBox", true);
loader.lazyRequireGetter(this, "PriorityLevels", "devtools/client/shared/components/NotificationBox", true);
const l10n = require("devtools/client/webconsole/webconsole-l10n");
// Constants used for defining the direction of JSTerm input history navigation.
const HISTORY_BACK = -1;
const HISTORY_FORWARD = 1;
-const XHTML_NS = "http://www.w3.org/1999/xhtml";
-
const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
-const VARIABLES_VIEW_URL = "chrome://devtools/content/shared/widgets/VariablesView.xul";
-
const PREF_INPUT_HISTORY_COUNT = "devtools.webconsole.inputHistoryCount";
const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
+function gSequenceId() {
+ return gSequenceId.n++;
+}
+gSequenceId.n = 0;
+
+const { Component } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
/**
* Create a JSTerminal (a JavaScript command line). This is attached to an
* existing HeadsUpDisplay (a Web Console instance). This code is responsible
- * with handling command line input, code evaluation and result output.
+ * with handling command line input and code evaluation.
*
* @constructor
* @param object webConsoleFrame
* The WebConsoleFrame object that owns this JSTerm instance.
*/
-function JSTerm(webConsoleFrame) {
- this.hud = webConsoleFrame;
- this.hudId = this.hud.hudId;
- this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
+class JSTerm extends Component {
+ static get propTypes() {
+ return {
+ hud: PropTypes.object.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ const {
+ hud,
+ } = props;
+ this.hud = hud;
+ this.hudId = this.hud.hudId;
+ this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
+ this._loadHistory();
+
+ /**
+ * Stores the data for the last completion.
+ * @type object
+ */
+ this.lastCompletion = { value: null };
+
+ this._keyPress = this._keyPress.bind(this);
+ this._inputEventHandler = this._inputEventHandler.bind(this);
+ this._focusEventHandler = this._focusEventHandler.bind(this);
+ this._blurEventHandler = this._blurEventHandler.bind(this);
+
+ this.SELECTED_FRAME = -1;
- this.lastCompletion = { value: null };
- this._loadHistory();
+ /**
+ * Array that caches the user input suggestions received from the server.
+ * @private
+ * @type array
+ */
+ this._autocompleteCache = null;
+
+ /**
+ * The input that caused the last request to the server, whose response is
+ * cached in the _autocompleteCache array.
+ * @private
+ * @type string
+ */
+ this._autocompleteQuery = null;
- this._objectActorsInVariablesViews = new Map();
+ /**
+ * The frameActorId used in the last autocomplete query. Whenever this changes
+ * the autocomplete cache must be invalidated.
+ * @private
+ * @type string
+ */
+ this._lastFrameActorId = null;
+
+ /**
+ * Last input value.
+ * @type string
+ */
+ this.lastInputValue = "";
+
+ /**
+ * Tells if the input node changed since the last focus.
+ *
+ * @private
+ * @type boolean
+ */
+ this._inputChanged = false;
- this._keyPress = this._keyPress.bind(this);
- this._inputEventHandler = this._inputEventHandler.bind(this);
- this._focusEventHandler = this._focusEventHandler.bind(this);
- this._onKeypressInVariablesView = this._onKeypressInVariablesView.bind(this);
- this._blurEventHandler = this._blurEventHandler.bind(this);
+ /**
+ * Tells if the autocomplete popup was navigated since the last open.
+ *
+ * @private
+ * @type boolean
+ */
+ this._autocompletePopupNavigated = false;
+
+ /**
+ * History of code that was executed.
+ * @type array
+ */
+ this.history = [];
+ this.autocompletePopup = null;
+ this.inputNode = null;
+ this.completeNode = null;
+
+ this.COMPLETE_FORWARD = 0;
+ this.COMPLETE_BACKWARD = 1;
+ this.COMPLETE_HINT_ONLY = 2;
+ this.COMPLETE_PAGEUP = 3;
+ this.COMPLETE_PAGEDOWN = 4;
+
+ EventEmitter.decorate(this);
+ hud.jsterm = this;
+ }
+
+ componentDidMount() {
+ if (!this.inputNode) {
+ return;
+ }
+
+ let autocompleteOptions = {
+ onSelect: this.onAutocompleteSelect.bind(this),
+ onClick: this.acceptProposedCompletion.bind(this),
+ listId: "webConsole_autocompletePopupListBox",
+ position: "top",
+ theme: "auto",
+ autoSelect: true
+ };
- EventEmitter.decorate(this);
-}
+ let doc = this.hud.document;
+ let toolbox = gDevTools.getToolbox(this.hud.owner.target);
+ let tooltipDoc = toolbox ? toolbox.doc : doc;
+ // The popup will be attached to the toolbox document or HUD document in the case
+ // such as the browser console which doesn't have a toolbox.
+ this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
+
+ this.inputBorderSize = this.inputNode.getBoundingClientRect().height -
+ this.inputNode.clientHeight;
+
+ // Update the character width and height needed for the popup offset
+ // calculations.
+ this._updateCharSize();
+
+ this.inputNode.addEventListener("keypress", this._keyPress);
+ this.inputNode.addEventListener("input", this._inputEventHandler);
+ this.inputNode.addEventListener("keyup", this._inputEventHandler);
+ this.inputNode.addEventListener("focus", this._focusEventHandler);
-JSTerm.prototype = {
- SELECTED_FRAME: -1,
+ if (!this.hud.isBrowserConsole) {
+ let okstring = l10n.getStr("selfxss.okstring");
+ let msg = l10n.getFormatStr("selfxss.msg", [okstring]);
+ this._onPaste = WebConsoleUtils.pasteHandlerGen(this.inputNode,
+ this.getNotificationBox(), msg, okstring);
+ this.inputNode.addEventListener("paste", this._onPaste);
+ this.inputNode.addEventListener("drop", this._onPaste);
+ }
+
+ this.hud.window.addEventListener("blur", this._blurEventHandler);
+ this.lastInputValue && this.setInputValue(this.lastInputValue);
+
+ this.focus();
+ }
+
+ shouldComponentUpdate() {
+ // XXX: For now, everything is handled in an imperative way and we only want React
+ // to do the initial rendering of the component.
+ // This should be modified when the actual refactoring will take place.
+ return false;
+ }
/**
* Load the console history from previous sessions.
* @private
*/
- _loadHistory: function() {
+ _loadHistory() {
this.history = [];
this.historyIndex = this.historyPlaceHolder = 0;
this.historyLoaded = asyncStorage.getItem("webConsoleHistory")
.then(value => {
if (Array.isArray(value)) {
// Since it was gotten asynchronously, there could be items already in
// the history. It's not likely but stick them onto the end anyway.
@@ -95,250 +219,83 @@ JSTerm.prototype = {
this.historyIndex = this.history.length;
// Holds the index of the history entry that the user is currently
// viewing. This is reset to this.history.length when this.execute()
// is invoked.
this.historyPlaceHolder = this.history.length;
}
}, console.error);
- },
+ }
/**
* Clear the console history altogether. Note that this will not affect
* other consoles that are already opened (since they have their own copy),
* but it will reset the array for all newly-opened consoles.
* @returns Promise
* Resolves once the changes have been persisted.
*/
- clearHistory: function() {
+ clearHistory() {
this.history = [];
this.historyIndex = this.historyPlaceHolder = 0;
return this.storeHistory();
- },
+ }
/**
* Stores the console history for future console instances.
* @returns Promise
* Resolves once the changes have been persisted.
*/
- storeHistory: function() {
+ storeHistory() {
return asyncStorage.setItem("webConsoleHistory", this.history);
- },
-
- /**
- * Stores the data for the last completion.
- * @type object
- */
- lastCompletion: null,
-
- /**
- * Array that caches the user input suggestions received from the server.
- * @private
- * @type array
- */
- _autocompleteCache: null,
-
- /**
- * The input that caused the last request to the server, whose response is
- * cached in the _autocompleteCache array.
- * @private
- * @type string
- */
- _autocompleteQuery: null,
-
- /**
- * The frameActorId used in the last autocomplete query. Whenever this changes
- * the autocomplete cache must be invalidated.
- * @private
- * @type string
- */
- _lastFrameActorId: null,
-
- /**
- * The Web Console sidebar.
- * @see this._createSidebar()
- * @see Sidebar.jsm
- */
- sidebar: null,
-
- /**
- * The Variables View instance shown in the sidebar.
- * @private
- * @type object
- */
- _variablesView: null,
-
- /**
- * Tells if you want the variables view UI updates to be lazy or not. Tests
- * disable lazy updates.
- *
- * @private
- * @type boolean
- */
- _lazyVariablesView: true,
-
- /**
- * Holds a map between VariablesView instances and sets of ObjectActor IDs
- * that have been retrieved from the server. This allows us to release the
- * objects when needed.
- *
- * @private
- * @type Map
- */
- _objectActorsInVariablesViews: null,
-
- /**
- * Last input value.
- * @type string
- */
- lastInputValue: "",
-
- /**
- * Tells if the input node changed since the last focus.
- *
- * @private
- * @type boolean
- */
- _inputChanged: false,
-
- /**
- * Tells if the autocomplete popup was navigated since the last open.
- *
- * @private
- * @type boolean
- */
- _autocompletePopupNavigated: false,
-
- /**
- * History of code that was executed.
- * @type array
- */
- history: null,
- autocompletePopup: null,
- inputNode: null,
- completeNode: null,
+ }
/**
* Getter for the element that holds the messages we display.
* @type nsIDOMElement
*/
get outputNode() {
return this.hud.outputNode;
- },
+ }
/**
* Getter for the debugger WebConsoleClient.
* @type object
*/
get webConsoleClient() {
return this.hud.webConsoleClient;
- },
-
- COMPLETE_FORWARD: 0,
- COMPLETE_BACKWARD: 1,
- COMPLETE_HINT_ONLY: 2,
- COMPLETE_PAGEUP: 3,
- COMPLETE_PAGEDOWN: 4,
-
- /**
- * Initialize the JSTerminal UI.
- */
- init: function() {
- let autocompleteOptions = {
- onSelect: this.onAutocompleteSelect.bind(this),
- onClick: this.acceptProposedCompletion.bind(this),
- listId: "webConsole_autocompletePopupListBox",
- position: "top",
- theme: "auto",
- autoSelect: true
- };
+ }
- let doc = this.hud.document;
- let toolbox = gDevTools.getToolbox(this.hud.owner.target);
- let tooltipDoc = toolbox ? toolbox.doc : doc;
- // The popup will be attached to the toolbox document or HUD document in the case
- // such as the browser console which doesn't have a toolbox.
- this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
- let inputContainer = doc.querySelector(".jsterm-input-container");
- this.completeNode = doc.querySelector(".jsterm-complete-node");
- this.inputNode = doc.querySelector(".jsterm-input-node");
- this.inputBorderSize = this.inputNode.getBoundingClientRect().height -
- this.inputNode.clientHeight;
- // Update the character width and height needed for the popup offset
- // calculations.
- this._updateCharSize();
-
- if (this.hud.isBrowserConsole &&
- !Services.prefs.getBoolPref("devtools.chrome.enabled")) {
- inputContainer.style.display = "none";
- } else {
- this.inputNode.addEventListener("keypress", this._keyPress);
- this.inputNode.addEventListener("input", this._inputEventHandler);
- this.inputNode.addEventListener("keyup", this._inputEventHandler);
- this.inputNode.addEventListener("focus", this._focusEventHandler);
- }
-
- if (!this.hud.isBrowserConsole) {
- let okstring = l10n.getStr("selfxss.okstring");
- let msg = l10n.getFormatStr("selfxss.msg", [okstring]);
- this._onPaste = WebConsoleUtils.pasteHandlerGen(this.inputNode,
- this.getNotificationBox(), msg, okstring);
- this.inputNode.addEventListener("paste", this._onPaste);
- this.inputNode.addEventListener("drop", this._onPaste);
- }
-
- this.hud.window.addEventListener("blur", this._blurEventHandler);
- this.lastInputValue && this.setInputValue(this.lastInputValue);
- },
-
- focus: function() {
- if (!this.inputNode.getAttribute("focused")) {
+ focus() {
+ if (this.inputNode && !this.inputNode.getAttribute("focused")) {
this.inputNode.focus();
}
- },
+ }
/**
* The JavaScript evaluation response handler.
*
* @private
* @param function [callback]
* Optional function to invoke when the evaluation result is added to
* the output.
* @param object response
* The message received from the server.
*/
- _executeResultCallback: function(callback, response) {
+ _executeResultCallback(callback, response) {
if (!this.hud) {
return;
}
if (response.error) {
- console.error("Evaluation error " + response.error + ": " +
- response.message);
+ console.error("Evaluation error " + response.error + ": " + response.message);
return;
}
let errorMessage = response.exceptionMessage;
- let errorDocURL = response.exceptionDocURL;
- let errorDocLink;
- if (errorDocURL) {
- errorMessage += " ";
- errorDocLink = this.hud.document.createElementNS(XHTML_NS, "a");
- errorDocLink.className = "learn-more-link webconsole-learn-more-link";
- errorDocLink.textContent = `[${l10n.getStr("webConsoleMoreInfoLabel")}]`;
- errorDocLink.title = errorDocURL.split("?")[0];
- errorDocLink.href = "#";
- errorDocLink.draggable = false;
- errorDocLink.addEventListener("click", () => {
- this.hud.owner.openLink(errorDocURL);
- });
- }
-
- // Wrap thrown strings in Error objects, so `throw "foo"` outputs
- // "Error: foo"
+ // Wrap thrown strings in Error objects, so `throw "foo"` outputs "Error: foo"
if (typeof response.exception === "string") {
errorMessage = new Error(errorMessage).toString();
}
let result = response.result;
let helperResult = response.helperResult;
let helperHasRawOutput = !!(helperResult || {}).rawOutput;
if (helperResult && helperResult.type) {
@@ -371,91 +328,46 @@ JSTerm.prototype = {
// Hide undefined results coming from JSTerm helper functions.
if (!errorMessage && result && typeof result == "object" &&
result.type == "undefined" &&
helperResult && !helperHasRawOutput) {
callback && callback();
return;
}
- if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
+ if (this.hud.newConsoleOutput) {
this.hud.newConsoleOutput.dispatchMessageAdd(response, true).then(callback);
- return;
}
- let msg = new Messages.JavaScriptEvalOutput(response,
- errorMessage, errorDocLink);
- this.hud.output.addMessage(msg);
-
- if (callback) {
- let oldFlushCallback = this.hud._flushCallback;
- this.hud._flushCallback = () => {
- callback(msg.element);
- if (oldFlushCallback) {
- oldFlushCallback();
- this.hud._flushCallback = oldFlushCallback;
- return true;
- }
-
- return false;
- };
- }
-
- msg._objectActors = new Set();
+ }
- if (WebConsoleUtils.isActorGrip(response.exception)) {
- msg._objectActors.add(response.exception.actor);
- }
-
- if (WebConsoleUtils.isActorGrip(result)) {
- msg._objectActors.add(result.actor);
- }
- },
-
- inspectObjectActor: function(objectActor) {
- if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
- this.hud.newConsoleOutput.dispatchMessageAdd({
- helperResult: {
- type: "inspectObject",
- object: objectActor
- }
- }, true);
- return this.hud.newConsoleOutput;
- }
-
- return this.openVariablesView({
- objectActor,
- label: VariablesView.getString(objectActor, {concise: true}),
- });
- },
+ inspectObjectActor(objectActor) {
+ this.hud.newConsoleOutput.dispatchMessageAdd({
+ helperResult: {
+ type: "inspectObject",
+ object: objectActor
+ }
+ }, true);
+ return this.hud.newConsoleOutput;
+ }
/**
* Execute a string. Execution happens asynchronously in the content process.
*
* @param string [executeString]
* The string you want to execute. If this is not provided, the current
* user input is used - taken from |this.getInputValue()|.
* @param function [callback]
* Optional function to invoke when the result is displayed.
* This is deprecated - please use the promise return value instead.
* @returns Promise
* Resolves with the message once the result is displayed.
*/
- execute: async function(executeString, callback) {
+ async execute(executeString, callback) {
let deferred = defer();
- let resultCallback;
- if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
- resultCallback = (msg) => deferred.resolve(msg);
- } else {
- resultCallback = (msg) => {
- deferred.resolve(msg);
- if (callback) {
- callback(msg);
- }
- };
- }
+ let resultCallback = msg => deferred.resolve(msg);
// attempt to execute the content of the inputNode
executeString = executeString || this.getInputValue();
if (!executeString) {
return null;
}
// Append a new value in the history of executed code, or overwrite the most
@@ -474,67 +386,60 @@ JSTerm.prototype = {
this.clearCompletion();
let selectedNodeActor = null;
let inspectorSelection = this.hud.owner.getInspectorSelection();
if (inspectorSelection && inspectorSelection.nodeFront) {
selectedNodeActor = inspectorSelection.nodeFront.actorID;
}
- if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
- const { ConsoleCommand } = require("devtools/client/webconsole/types");
- let message = new ConsoleCommand({
- messageText: executeString,
- });
- this.hud.proxy.dispatchMessageAdd(message);
- } else {
- let message = new Messages.Simple(executeString, {
- category: "input",
- severity: "log",
- });
- this.hud.output.addMessage(message);
- }
+ const { ConsoleCommand } = require("devtools/client/webconsole/types");
+ let message = new ConsoleCommand({
+ messageText: executeString,
+ });
+ this.hud.proxy.dispatchMessageAdd(message);
+
let onResult = this._executeResultCallback.bind(this, resultCallback);
let options = {
frame: this.SELECTED_FRAME,
selectedNodeActor: selectedNodeActor,
};
const mappedString = await this.hud.owner.getMappedExpression(executeString);
this.requestEvaluation(mappedString, options).then(onResult, onResult);
return deferred.promise;
- },
+ }
/**
* Request a JavaScript string evaluation from the server.
*
* @param string str
* String to execute.
* @param object [options]
* Options for evaluation:
* - bindObjectActor: tells the ObjectActor ID for which you want to do
* the evaluation. The Debugger.Object of the OA will be bound to
* |_self| during evaluation, such that it's usable in the string you
* execute.
* - frame: tells the stackframe depth to evaluate the string in. If
* the jsdebugger is paused, you can pick the stackframe to be used for
- * evaluation. Use |this.SELECTED_FRAME| to always pick the
+ * evaluation. Use |this.SELECTED_FRAME| to always pick th;
* user-selected stackframe.
* If you do not provide a |frame| the string will be evaluated in the
* global content window.
* - selectedNodeActor: tells the NodeActor ID of the current selection
* in the Inspector, if such a selection exists. This is used by
* helper functions that can evaluate on the current selection.
* @return object
* A promise object that is resolved when the server response is
* received.
*/
- requestEvaluation: function(str, options = {}) {
+ requestEvaluation(str, options = {}) {
let deferred = defer();
function onResult(response) {
if (!response.error) {
deferred.resolve(response);
} else {
deferred.reject(response);
}
@@ -549,563 +454,179 @@ JSTerm.prototype = {
bindObjectActor: options.bindObjectActor,
frameActor: frameActor,
selectedNodeActor: options.selectedNodeActor,
selectedObjectActor: options.selectedObjectActor,
};
this.webConsoleClient.evaluateJSAsync(str, onResult, evalOptions);
return deferred.promise;
- },
+ }
/**
* Copy the object/variable by invoking the server
* which invokes the `copy(variable)` command and makes it
* available in the clipboard
* @param evalString - string which has the evaluation string to be copied
* @param options - object - Options for evaluation
* @return object
* A promise object that is resolved when the server response is
* received.
*/
- copyObject: function(evalString, evalOptions) {
+ copyObject(evalString, evalOptions) {
return this.webConsoleClient.evaluateJSAsync(`copy(${evalString})`,
null, evalOptions);
- },
+ }
/**
* Retrieve the FrameActor ID given a frame depth.
*
* @param number frame
* Frame depth.
* @return string|null
* The FrameActor ID for the given frame depth.
*/
- getFrameActor: function(frame) {
+ getFrameActor(frame) {
let state = this.hud.owner.getDebuggerFrames();
if (!state) {
return null;
}
let grip;
if (frame == this.SELECTED_FRAME) {
grip = state.frames[state.selected];
} else {
grip = state.frames[frame];
}
return grip ? grip.actor : null;
- },
-
- /**
- * Opens a new variables view that allows the inspection of the given object.
- *
- * @param object options
- * Options for the variables view:
- * - objectActor: grip of the ObjectActor you want to show in the
- * variables view.
- * - rawObject: the raw object you want to show in the variables view.
- * - label: label to display in the variables view for inspected
- * object.
- * - hideFilterInput: optional boolean, |true| if you want to hide the
- * variables view filter input.
- * - targetElement: optional nsIDOMElement to append the variables view
- * to. An iframe element is used as a container for the view. If this
- * option is not used, then the variables view opens in the sidebar.
- * - autofocus: optional boolean, |true| if you want to give focus to
- * the variables view window after open, |false| otherwise.
- * @return object
- * A promise object that is resolved when the variables view has
- * opened. The new variables view instance is given to the callbacks.
- */
- openVariablesView: function(options) {
- // Bail out if the side bar doesn't exist.
- if (!this.hud.document.querySelector("#webconsole-sidebar")) {
- return Promise.resolve(null);
- }
-
- let onContainerReady = (window) => {
- let container = window.document.querySelector("#variables");
- let view = this._variablesView;
- if (!view || options.targetElement) {
- let viewOptions = {
- container: container,
- hideFilterInput: options.hideFilterInput,
- };
- view = this._createVariablesView(viewOptions);
- if (!options.targetElement) {
- this._variablesView = view;
- window.addEventListener("keypress", this._onKeypressInVariablesView);
- }
- }
- options.view = view;
- this._updateVariablesView(options);
-
- if (!options.targetElement && options.autofocus) {
- window.focus();
- }
-
- this.emit("variablesview-open", {view, options});
- return view;
- };
-
- let openPromise;
- if (options.targetElement) {
- let deferred = defer();
- openPromise = deferred.promise;
- let document = options.targetElement.ownerDocument;
- let iframe = document.createElementNS(XHTML_NS, "iframe");
-
- iframe.addEventListener("load", function() {
- iframe.style.visibility = "visible";
- deferred.resolve(iframe.contentWindow);
- }, {capture: true, once: true});
-
- iframe.flex = 1;
- iframe.style.visibility = "hidden";
- iframe.setAttribute("src", VARIABLES_VIEW_URL);
- options.targetElement.appendChild(iframe);
- } else {
- if (!this.sidebar) {
- this._createSidebar();
- }
- openPromise = this._addVariablesViewSidebarTab();
- }
-
- return openPromise.then(onContainerReady);
- },
-
- /**
- * Create the Web Console sidebar.
- *
- * @see devtools/framework/sidebar.js
- * @private
- */
- _createSidebar: function() {
- let tabbox = this.hud.document.querySelector("#webconsole-sidebar");
- this.sidebar = new ToolSidebar(tabbox, this, "webconsole");
- this.sidebar.show();
- this.emit("sidebar-opened");
- },
-
- /**
- * Add the variables view tab to the sidebar.
- *
- * @private
- * @return object
- * A promise object for the adding of the new tab.
- */
- _addVariablesViewSidebarTab: function() {
- let deferred = defer();
-
- let onTabReady = () => {
- let window = this.sidebar.getWindowForTab("variablesview");
- deferred.resolve(window);
- };
-
- let tabPanel = this.sidebar.getTabPanel("variablesview");
- if (tabPanel) {
- if (this.sidebar.getCurrentTabID() == "variablesview") {
- onTabReady();
- } else {
- this.sidebar.once("variablesview-selected", onTabReady);
- this.sidebar.select("variablesview");
- }
- } else {
- this.sidebar.once("variablesview-ready", onTabReady);
- this.sidebar.addTab("variablesview", VARIABLES_VIEW_URL, {selected: true});
- }
-
- return deferred.promise;
- },
-
- /**
- * The keypress event handler for the Variables View sidebar. Currently this
- * is used for removing the sidebar when Escape is pressed.
- *
- * @private
- * @param nsIDOMEvent event
- * The keypress DOM event object.
- */
- _onKeypressInVariablesView: function(event) {
- let tag = event.target.nodeName;
- if (event.keyCode != KeyCodes.DOM_VK_ESCAPE || event.shiftKey ||
- event.altKey || event.ctrlKey || event.metaKey ||
- ["input", "textarea", "select", "textbox"].indexOf(tag) > -1) {
- return;
- }
-
- this._sidebarDestroy();
- this.focus();
- event.stopPropagation();
- },
-
- /**
- * Create a variables view instance.
- *
- * @private
- * @param object options
- * Options for the new Variables View instance:
- * - container: the DOM element where the variables view is inserted.
- * - hideFilterInput: boolean, if true the variables filter input is
- * hidden.
- * @return object
- * The new Variables View instance.
- */
- _createVariablesView: function(options) {
- let view = new VariablesView(options.container);
- view.toolbox = gDevTools.getToolbox(this.hud.owner.target);
- view.searchPlaceholder = l10n.getStr("propertiesFilterPlaceholder");
- view.emptyText = l10n.getStr("emptyPropertiesList");
- view.searchEnabled = !options.hideFilterInput;
- view.lazyEmpty = this._lazyVariablesView;
-
- VariablesViewController.attach(view, {
- getEnvironmentClient: grip => {
- return new EnvironmentClient(this.hud.proxy.client, grip);
- },
- getObjectClient: grip => {
- return new ObjectClient(this.hud.proxy.client, grip);
- },
- getLongStringClient: grip => {
- return this.webConsoleClient.longString(grip);
- },
- releaseActor: actor => {
- this.hud._releaseObject(actor);
- },
- simpleValueEvalMacro: simpleValueEvalMacro,
- overrideValueEvalMacro: overrideValueEvalMacro,
- getterOrSetterEvalMacro: getterOrSetterEvalMacro,
- });
-
- // Relay events from the VariablesView.
- view.on("fetched", (type, variableObject) => {
- this.emit("variablesview-fetched", variableObject);
- });
-
- return view;
- },
-
- /**
- * Update the variables view.
- *
- * @private
- * @param object options
- * Options for updating the variables view:
- * - view: the view you want to update.
- * - objectActor: the grip of the new ObjectActor you want to show in
- * the view.
- * - rawObject: the new raw object you want to show.
- * - label: the new label for the inspected object.
- */
- _updateVariablesView: function(options) {
- let view = options.view;
- view.empty();
-
- // We need to avoid pruning the object inspection starting point.
- // That one is pruned when the console message is removed.
- view.controller.releaseActors(actor => {
- return view._consoleLastObjectActor != actor;
- });
-
- if (options.objectActor &&
- (!this.hud.isBrowserConsole ||
- Services.prefs.getBoolPref("devtools.chrome.enabled"))) {
- // Make sure eval works in the correct context.
- view.eval = this._variablesViewEvaluate.bind(this, options);
- view.switch = this._variablesViewSwitch.bind(this, options);
- view.delete = this._variablesViewDelete.bind(this, options);
- } else {
- view.eval = null;
- view.switch = null;
- view.delete = null;
- }
-
- let { variable, expanded } = view.controller.setSingleVariable(options);
- variable.evaluationMacro = simpleValueEvalMacro;
-
- if (options.objectActor) {
- view._consoleLastObjectActor = options.objectActor.actor;
- } else if (options.rawObject) {
- view._consoleLastObjectActor = null;
- } else {
- throw new Error(
- "Variables View cannot open without giving it an object display.");
- }
-
- expanded.then(() => {
- this.emit("variablesview-updated", view, options);
- });
- },
-
- /**
- * The evaluation function used by the variables view when editing a property
- * value.
- *
- * @private
- * @param object options
- * The options used for |this._updateVariablesView()|.
- * @param object variableObject
- * The Variable object instance for the edited property.
- * @param string value
- * The value the edited property was changed to.
- */
- _variablesViewEvaluate: function(options, variableObject, value) {
- let updater = this._updateVariablesView.bind(this, options);
- let onEval = this._silentEvalCallback.bind(this, updater);
- let string = variableObject.evaluationMacro(variableObject, value);
-
- let evalOptions = {
- frame: this.SELECTED_FRAME,
- bindObjectActor: options.objectActor.actor,
- };
-
- this.requestEvaluation(string, evalOptions).then(onEval, onEval);
- },
-
- /**
- * The property deletion function used by the variables view when a property
- * is deleted.
- *
- * @private
- * @param object options
- * The options used for |this._updateVariablesView()|.
- * @param object variableObject
- * The Variable object instance for the deleted property.
- */
- _variablesViewDelete: function(options, variableObject) {
- let onEval = this._silentEvalCallback.bind(this, null);
-
- let evalOptions = {
- frame: this.SELECTED_FRAME,
- bindObjectActor: options.objectActor.actor,
- };
-
- this.requestEvaluation("delete _self" +
- variableObject.symbolicName, evalOptions).then(onEval, onEval);
- },
-
- /**
- * The property rename function used by the variables view when a property
- * is renamed.
- *
- * @private
- * @param object options
- * The options used for |this._updateVariablesView()|.
- * @param object variableObject
- * The Variable object instance for the renamed property.
- * @param string newName
- * The new name for the property.
- */
- _variablesViewSwitch: function(options, variableObject, newName) {
- let updater = this._updateVariablesView.bind(this, options);
- let onEval = this._silentEvalCallback.bind(this, updater);
-
- let evalOptions = {
- frame: this.SELECTED_FRAME,
- bindObjectActor: options.objectActor.actor,
- };
-
- let newSymbolicName =
- variableObject.ownerView.symbolicName + '["' + newName + '"]';
- if (newSymbolicName == variableObject.symbolicName) {
- return;
- }
-
- let code = "_self" + newSymbolicName + " = _self" +
- variableObject.symbolicName + ";" + "delete _self" +
- variableObject.symbolicName;
-
- this.requestEvaluation(code, evalOptions).then(onEval, onEval);
- },
-
- /**
- * A noop callback for JavaScript evaluation. This method releases any
- * result ObjectActors that come from the server for evaluation requests. This
- * is used for editing, renaming and deleting properties in the variables
- * view.
- *
- * Exceptions are displayed in the output.
- *
- * @private
- * @param function callback
- * Function to invoke once the response is received.
- * @param object response
- * The response packet received from the server.
- */
- _silentEvalCallback: function(callback, response) {
- if (response.error) {
- console.error("Web Console evaluation failed. " + response.error + ":" +
- response.message);
-
- callback && callback(response);
- return;
- }
-
- if (response.exceptionMessage) {
- let message = new Messages.Simple(response.exceptionMessage, {
- category: "output",
- severity: "error",
- timestamp: response.timestamp,
- });
- this.hud.output.addMessage(message);
- message._objectActors = new Set();
- if (WebConsoleUtils.isActorGrip(response.exception)) {
- message._objectActors.add(response.exception.actor);
- }
- }
-
- let helper = response.helperResult || { type: null };
- let helperGrip = null;
- if (helper.type == "inspectObject") {
- helperGrip = helper.object;
- }
-
- let grips = [response.result, helperGrip];
- for (let grip of grips) {
- if (WebConsoleUtils.isActorGrip(grip)) {
- this.hud._releaseObject(grip.actor);
- }
- }
-
- callback && callback(response);
- },
+ }
/**
* Clear the Web Console output.
*
* This method emits the "messages-cleared" notification.
*
* @param boolean clearStorage
* True if you want to clear the console messages storage associated to
* this Web Console.
*/
- clearOutput: function(clearStorage) {
- let hud = this.hud;
+ clearOutput(clearStorage) {
+ if (this.hud && this.hud.newConsoleOutput) {
+ this.hud.newConsoleOutput.dispatchMessagesClear();
+ }
- if (hud.NEW_CONSOLE_OUTPUT_ENABLED) {
- hud.newConsoleOutput.dispatchMessagesClear();
- } else {
- let outputNode = hud.outputNode;
- let node;
- while ((node = outputNode.firstChild)) {
- hud.removeOutputMessage(node);
- }
-
- hud.groupDepth = 0;
- hud._outputQueue.forEach(hud._destroyItem, hud);
- hud._outputQueue = [];
- hud._repeatNodes = {};
- }
this.webConsoleClient.clearNetworkRequests();
if (clearStorage) {
this.webConsoleClient.clearMessagesCache();
}
- this._sidebarDestroy();
this.focus();
this.emit("messages-cleared");
- },
+ }
/**
* Remove all of the private messages from the Web Console output.
*
* This method emits the "private-messages-cleared" notification.
*/
- clearPrivateMessages: function() {
- if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
+ clearPrivateMessages() {
+ if (this.hud && this.hud.newConsoleOutput) {
this.hud.newConsoleOutput.dispatchPrivateMessagesClear();
- } else {
- let nodes = this.hud.outputNode.querySelectorAll(".message[private]");
- for (let node of nodes) {
- this.hud.removeOutputMessage(node);
- }
+ this.emit("private-messages-cleared");
}
- this.emit("private-messages-cleared");
- },
+ }
/**
* Updates the size of the input field (command line) to fit its contents.
*
* @returns void
*/
- resizeInput: function() {
+ resizeInput() {
+ if (!this.inputNode) {
+ return;
+ }
+
let inputNode = this.inputNode;
// Reset the height so that scrollHeight will reflect the natural height of
// the contents of the input field.
inputNode.style.height = "auto";
// Now resize the input field to fit its contents.
// TODO: remove `inputNode.inputField.scrollHeight` when the old
// console UI is removed. See bug 1381834
let scrollHeight = inputNode.inputField ?
inputNode.inputField.scrollHeight : inputNode.scrollHeight;
if (scrollHeight > 0) {
inputNode.style.height = (scrollHeight + this.inputBorderSize) + "px";
}
- },
+ }
/**
* Sets the value of the input field (command line), and resizes the field to
* fit its contents. This method is preferred over setting "inputNode.value"
* directly, because it correctly resizes the field.
*
* @param string newValue
* The new value to set.
* @returns void
*/
- setInputValue: function(newValue) {
+ setInputValue(newValue) {
+ if (!this.inputNode) {
+ return;
+ }
+
this.inputNode.value = newValue;
this.lastInputValue = newValue;
this.completeNode.value = "";
this.resizeInput();
this._inputChanged = true;
this.emit("set-input-value");
- },
+ }
/**
* Gets the value from the input field
* @returns string
*/
- getInputValue: function() {
- return this.inputNode.value || "";
- },
+ getInputValue() {
+ return this.inputNode ? this.inputNode.value || "" : "";
+ }
/**
* The inputNode "input" and "keyup" event handler.
* @private
*/
- _inputEventHandler: function() {
+ _inputEventHandler() {
if (this.lastInputValue != this.getInputValue()) {
this.resizeInput();
this.complete(this.COMPLETE_HINT_ONLY);
this.lastInputValue = this.getInputValue();
this._inputChanged = true;
}
- },
+ }
/**
* The window "blur" event handler.
* @private
*/
- _blurEventHandler: function() {
+ _blurEventHandler() {
if (this.autocompletePopup) {
this.clearCompletion();
}
- },
+ }
/* eslint-disable complexity */
/**
* The inputNode "keypress" event handler.
*
* @private
* @param nsIDOMEvent event
*/
- _keyPress: function(event) {
+ _keyPress(event) {
let inputNode = this.inputNode;
let inputValue = this.getInputValue();
let inputUpdated = false;
if (event.ctrlKey) {
switch (event.charCode) {
case 101:
// control-e
@@ -1173,20 +694,16 @@ JSTerm.prototype = {
}
switch (event.keyCode) {
case KeyCodes.DOM_VK_ESCAPE:
if (this.autocompletePopup.isOpen) {
this.clearCompletion();
event.preventDefault();
event.stopPropagation();
- } else if (this.sidebar) {
- this._sidebarDestroy();
- event.preventDefault();
- event.stopPropagation();
}
break;
case KeyCodes.DOM_VK_RETURN:
if (this._autocompletePopupNavigated &&
this.autocompletePopup.isOpen &&
this.autocompletePopup.selectedIndex > -1) {
this.acceptProposedCompletion();
@@ -1313,37 +830,37 @@ JSTerm.prototype = {
} else if (this._inputChanged) {
this.updateCompleteNode(l10n.getStr("Autocomplete.blank"));
event.preventDefault();
}
break;
default:
break;
}
- },
+ }
/* eslint-enable complexity */
/**
* The inputNode "focus" event handler.
* @private
*/
- _focusEventHandler: function() {
+ _focusEventHandler() {
this._inputChanged = false;
- },
+ }
/**
* Go up/down the history stack of input values.
*
* @param number direction
* History navigation direction: HISTORY_BACK or HISTORY_FORWARD.
*
* @returns boolean
* True if the input value changed, false otherwise.
*/
- historyPeruse: function(direction) {
+ historyPeruse(direction) {
if (!this.history.length) {
return false;
}
// Up Arrow key
if (direction == HISTORY_BACK) {
if (this.historyPlaceHolder <= 0) {
return false;
@@ -1367,67 +884,67 @@ JSTerm.prototype = {
let inputVal = this.history[++this.historyPlaceHolder];
this.setInputValue(inputVal);
} else {
throw new Error("Invalid argument 0");
}
return true;
- },
+ }
/**
* Test for multiline input.
*
* @return boolean
* True if CR or LF found in node value; else false.
*/
- hasMultilineInput: function() {
+ hasMultilineInput() {
return /[\r\n]/.test(this.getInputValue());
- },
+ }
/**
* Check if the caret is at a location that allows selecting the previous item
* in history when the user presses the Up arrow key.
*
* @return boolean
* True if the caret is at a location that allows selecting the
* previous item in history when the user presses the Up arrow key,
* otherwise false.
*/
- canCaretGoPrevious: function() {
+ canCaretGoPrevious() {
let node = this.inputNode;
if (node.selectionStart != node.selectionEnd) {
return false;
}
let multiline = /[\r\n]/.test(node.value);
return node.selectionStart == 0 ? true :
node.selectionStart == node.value.length && !multiline;
- },
+ }
/**
* Check if the caret is at a location that allows selecting the next item in
* history when the user presses the Down arrow key.
*
* @return boolean
* True if the caret is at a location that allows selecting the next
* item in history when the user presses the Down arrow key, otherwise
* false.
*/
- canCaretGoNext: function() {
+ canCaretGoNext() {
let node = this.inputNode;
if (node.selectionStart != node.selectionEnd) {
return false;
}
let multiline = /[\r\n]/.test(node.value);
return node.selectionStart == node.value.length ? true :
node.selectionStart == 0 && !multiline;
- },
+ }
/**
* Completes the current typed text in the inputNode. Completion is performed
* only if the selection/cursor is at the end of the string. If no completion
* is found, the current inputNode value and cursor/selection stay.
*
* @param int type possible values are
* - this.COMPLETE_FORWARD: If there is more than one possible completion
@@ -1453,33 +970,33 @@ JSTerm.prototype = {
* is set from the current cursor position to the end of the
* completed text.
* @param function callback
* Optional function invoked when the autocomplete properties are
* updated.
* @returns boolean true if there existed a completion for the current input,
* or false otherwise.
*/
- complete: function(type, callback) {
+ complete(type, callback) {
let inputNode = this.inputNode;
let inputValue = this.getInputValue();
let frameActor = this.getFrameActor(this.SELECTED_FRAME);
// If the inputNode has no value, then don't try to complete on it.
if (!inputValue) {
this.clearCompletion();
callback && callback(this);
this.emit("autocomplete-updated");
return false;
}
// Only complete if the selection is empty.
if (inputNode.selectionStart != inputNode.selectionEnd) {
this.clearCompletion();
- callback && callback(this);
+ this.callback && callback(this);
this.emit("autocomplete-updated");
return false;
}
// Update the completion results.
if (this.lastCompletion.value != inputValue ||
frameActor != this._lastFrameActorId) {
this._updateCompletionResult(type, callback);
@@ -1500,43 +1017,43 @@ JSTerm.prototype = {
popup.selectPreviousPageItem();
} else if (type == this.COMPLETE_PAGEDOWN) {
popup.selectNextPageItem();
}
callback && callback(this);
this.emit("autocomplete-updated");
return accepted || popup.itemCount > 0;
- },
+ }
/**
* Update the completion result. This operation is performed asynchronously by
* fetching updated results from the content process.
*
* @private
* @param int type
* Completion type. See this.complete() for details.
* @param function [callback]
* Optional, function to invoke when completion results are received.
*/
- _updateCompletionResult: function(type, callback) {
+ _updateCompletionResult(type, callback) {
let frameActor = this.getFrameActor(this.SELECTED_FRAME);
if (this.lastCompletion.value == this.getInputValue() &&
frameActor == this._lastFrameActorId) {
return;
}
let requestId = gSequenceId();
let cursor = this.inputNode.selectionStart;
let input = this.getInputValue().substring(0, cursor);
let cache = this._autocompleteCache;
// If the current input starts with the previous input, then we already
// have a list of suggestions and we just need to filter the cached
- // suggestions. When the current input ends with a non-alphanumeric
+ // suggestions. When the current input ends with a non-alphanumeri;
// character we ask the server again for suggestions.
// Check if last character is non-alphanumeric
if (!/[a-zA-Z0-9]$/.test(input) || frameActor != this._lastFrameActorId) {
this._autocompleteQuery = null;
this._autocompleteCache = null;
}
@@ -1573,32 +1090,32 @@ JSTerm.prototype = {
value: null,
};
let autocompleteCallback =
this._receiveAutocompleteProperties.bind(this, requestId, callback);
this.webConsoleClient.autocomplete(
input, cursor, autocompleteCallback, frameActor);
- },
+ }
/**
* Handler for the autocompletion results. This method takes
* the completion result received from the server and updates the UI
* accordingly.
*
* @param number requestId
* Request ID.
* @param function [callback=null]
* Optional, function to invoke when the completion result is received.
* @param object message
* The JSON message which holds the completion results received from
* the content process.
*/
- _receiveAutocompleteProperties: function(requestId, callback, message) {
+ _receiveAutocompleteProperties(requestId, callback, message) {
let inputNode = this.inputNode;
let inputValue = this.getInputValue();
if (this.lastCompletion.value == inputValue ||
requestId != this.lastCompletion.requestId) {
return;
}
// Cache whatever came from the server if the last char is
// alphanumeric or '.'
@@ -1652,62 +1169,65 @@ JSTerm.prototype = {
} else if (completionType == this.COMPLETE_BACKWARD) {
popup.selectPreviousItem();
} else if (completionType == this.COMPLETE_FORWARD) {
popup.selectNextItem();
}
callback && callback(this);
this.emit("autocomplete-updated");
- },
+ }
- onAutocompleteSelect: function() {
+ onAutocompleteSelect() {
// Render the suggestion only if the cursor is at the end of the input.
if (this.inputNode.selectionStart != this.getInputValue().length) {
return;
}
let currentItem = this.autocompletePopup.selectedItem;
if (currentItem && this.lastCompletion.value) {
let suffix =
currentItem.label.substring(this.lastCompletion.matchProp.length);
this.updateCompleteNode(suffix);
} else {
this.updateCompleteNode("");
}
- },
+ }
/**
* Clear the current completion information and close the autocomplete popup,
* if needed.
*/
- clearCompletion: function() {
- this.autocompletePopup.clearItems();
+ clearCompletion() {
this.lastCompletion = { value: null };
this.updateCompleteNode("");
- if (this.autocompletePopup.isOpen) {
- // Trigger a blur/focus of the JSTerm input to force screen readers to read the
- // value again.
- this.inputNode.blur();
- this.autocompletePopup.once("popup-closed", () => {
- this.inputNode.focus();
- });
- this.autocompletePopup.hidePopup();
- this._autocompletePopupNavigated = false;
+ if (this.autocompletePopup) {
+ this.autocompletePopup.clearItems();
+
+ if (this.autocompletePopup.isOpen) {
+ // Trigger a blur/focus of the JSTerm input to force screen readers to read the
+ // value again.
+ this.inputNode.blur();
+ this.autocompletePopup.once("popup-closed", () => {
+ this.inputNode.focus();
+ });
+ this.autocompletePopup.hidePopup();
+ this._autocompletePopupNavigated = false;
+ }
}
- },
+ }
/**
* Accept the proposed input completion.
*
* @return boolean
* True if there was a selected completion item and the input value
* was updated, false otherwise.
*/
- acceptProposedCompletion: function() {
+ acceptProposedCompletion() {
let updated = false;
let currentItem = this.autocompletePopup.selectedItem;
if (currentItem && this.lastCompletion.value) {
let suffix =
currentItem.label.substring(this.lastCompletion.matchProp.length);
let cursor = this.inputNode.selectionStart;
let value = this.getInputValue();
@@ -1716,152 +1236,138 @@ JSTerm.prototype = {
let newCursor = cursor + suffix.length;
this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
updated = true;
}
this.clearCompletion();
return updated;
- },
+ }
/**
* Update the node that displays the currently selected autocomplete proposal.
*
* @param string suffix
* The proposed suffix for the inputNode value.
*/
- updateCompleteNode: function(suffix) {
+ updateCompleteNode(suffix) {
+ if (!this.completeNode) {
+ return;
+ }
+
// completion prefix = input, with non-control chars replaced by spaces
let prefix = suffix ? this.getInputValue().replace(/[\S]/g, " ") : "";
this.completeNode.value = prefix + suffix;
- },
+ }
/**
* Calculates the width and height of a single character of the input box.
* This will be used in opening the popup at the correct offset.
*
* @private
*/
- _updateCharSize: function() {
+ _updateCharSize() {
let doc = this.hud.document;
- let tempLabel = doc.createElementNS(XHTML_NS, "span");
+ let tempLabel = doc.createElement("span");
let style = tempLabel.style;
style.position = "fixed";
style.padding = "0";
style.margin = "0";
style.width = "auto";
style.color = "transparent";
WebConsoleUtils.copyTextStyles(this.inputNode, tempLabel);
tempLabel.textContent = "x";
doc.documentElement.appendChild(tempLabel);
this._inputCharWidth = tempLabel.offsetWidth;
tempLabel.remove();
// Calculate the width of the chevron placed at the beginning of the input
// box. Remove 4 more pixels to accomodate the padding of the popup.
this._chevronWidth = +doc.defaultView.getComputedStyle(this.inputNode)
.paddingLeft.replace(/[^0-9.]/g, "") - 4;
- },
+ }
/**
* Build the notification box as soon as needed.
*/
- getNotificationBox: function() {
+ getNotificationBox() {
if (this._notificationBox) {
return this._notificationBox;
}
let box = this.hud.document.getElementById("webconsole-notificationbox");
- if (box.tagName === "notificationbox") {
- // Here we are in the old console frontend (e.g. browser console), so we
- // can directly return the notificationbox.
- return box;
- }
-
let toolbox = gDevTools.getToolbox(this.hud.owner.target);
// Render NotificationBox and assign priority levels to it.
this._notificationBox = Object.assign(
toolbox.ReactDOM.render(toolbox.React.createElement(NotificationBox), box),
PriorityLevels);
return this._notificationBox;
- },
+ }
+
+ destroy() {
+ this.clearCompletion();
- /**
- * Destroy the sidebar.
- * @private
- */
- _sidebarDestroy: function() {
- if (this._variablesView) {
- this._variablesView.controller.releaseActors();
- this._variablesView = null;
+ this.webConsoleClient.clearNetworkRequests();
+ if (this.hud.outputNode) {
+ // We do this because it's much faster than letting React handle the ConsoleOutput
+ // unmounting.
+ this.hud.outputNode.innerHTML = "";
}
- if (this.sidebar) {
- this.sidebar.hide();
- this.sidebar.destroy();
- this.sidebar = null;
+ if (this.autocompletePopup) {
+ this.autocompletePopup.destroy();
+ this.autocompletePopup = null;
}
- this.emit("sidebar-closed");
- },
+ if (this.inputNode) {
+ if (this._onPaste) {
+ this.inputNode.removeEventListener("paste", this._onPaste);
+ this.inputNode.removeEventListener("drop", this._onPaste);
+ this._onPaste = null;
+ }
- /**
- * Destroy the JSTerm object. Call this method to avoid memory leaks.
- */
- destroy: function() {
- this._sidebarDestroy();
-
- this.clearCompletion();
-
- if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
- this.webConsoleClient.clearNetworkRequests();
- this.hud.outputNode.innerHTML = "";
- } else {
- this.clearOutput();
+ this.inputNode.removeEventListener("keypress", this._keyPress);
+ this.inputNode.removeEventListener("input", this._inputEventHandler);
+ this.inputNode.removeEventListener("keyup", this._inputEventHandler);
+ this.inputNode.removeEventListener("focus", this._focusEventHandler);
+ this.hud.window.removeEventListener("blur", this._blurEventHandler);
}
- this.autocompletePopup.destroy();
- this.autocompletePopup = null;
+ this.hud = null;
+ }
- if (this._onPaste) {
- this.inputNode.removeEventListener("paste", this._onPaste);
- this.inputNode.removeEventListener("drop", this._onPaste);
- this._onPaste = null;
+ render() {
+ if (this.props.hud.isBrowserConsole &&
+ !Services.prefs.getBoolPref("devtools.chrome.enabled")) {
+ return null;
}
- this.inputNode.removeEventListener("keypress", this._keyPress);
- this.inputNode.removeEventListener("input", this._inputEventHandler);
- this.inputNode.removeEventListener("keyup", this._inputEventHandler);
- this.inputNode.removeEventListener("focus", this._focusEventHandler);
- this.hud.window.removeEventListener("blur", this._blurEventHandler);
-
- this.hud = null;
- },
-};
-
-function gSequenceId() {
- return gSequenceId.n++;
-}
-gSequenceId.n = 0;
-exports.gSequenceId = gSequenceId;
-
-/**
- * @see VariablesView.simpleValueEvalMacro
- */
-function simpleValueEvalMacro(item, currentString) {
- return VariablesView.simpleValueEvalMacro(item, currentString, "_self");
+ return [
+ dom.div({id: "webconsole-notificationbox", key: "notification"}),
+ dom.div({
+ className: "jsterm-input-container",
+ key: "jsterm-container",
+ style: {direction: "ltr"}
+ },
+ dom.textarea({
+ className: "jsterm-complete-node devtools-monospace",
+ key: "complete",
+ tabIndex: "-1",
+ ref: node => {
+ this.completeNode = node;
+ },
+ }),
+ dom.textarea({
+ className: "jsterm-input-node devtools-monospace",
+ key: "input",
+ tabIndex: "0",
+ rows: "1",
+ "aria-autocomplete": "list",
+ ref: node => {
+ this.inputNode = node;
+ },
+ })
+ ),
+ ];
+ }
}
-/**
- * @see VariablesView.overrideValueEvalMacro
- */
-function overrideValueEvalMacro(item, currentString) {
- return VariablesView.overrideValueEvalMacro(item, currentString, "_self");
-}
-
-/**
- * @see VariablesView.getterOrSetterEvalMacro
- */
-function getterOrSetterEvalMacro(item, currentString) {
- return VariablesView.getterOrSetterEvalMacro(item, currentString, "_self");
-}
-
-exports.JSTerm = JSTerm;
+module.exports = JSTerm;
--- a/devtools/client/webconsole/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output-wrapper.js
@@ -13,42 +13,43 @@ const { createContextMenu } = require("d
const { configureStore } = require("devtools/client/webconsole/store");
const { isPacketPrivate } = require("devtools/client/webconsole/utils/messages");
const { getAllMessagesById, getMessage } = require("devtools/client/webconsole/selectors/messages");
const EventEmitter = require("devtools/shared/event-emitter");
const ConsoleOutput = createFactory(require("devtools/client/webconsole/components/ConsoleOutput"));
const FilterBar = createFactory(require("devtools/client/webconsole/components/FilterBar"));
const SideBar = createFactory(require("devtools/client/webconsole/components/SideBar"));
+const JSTerm = createFactory(require("devtools/client/webconsole/components/JSTerm"));
let store = null;
-function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, document) {
+function NewConsoleOutputWrapper(parentNode, hud, toolbox, owner, document) {
EventEmitter.decorate(this);
this.parentNode = parentNode;
- this.jsterm = jsterm;
+ this.hud = hud;
this.toolbox = toolbox;
this.owner = owner;
this.document = document;
this.init = this.init.bind(this);
this.queuedMessageAdds = [];
this.queuedMessageUpdates = [];
this.queuedRequestUpdates = [];
this.throttledDispatchPromise = null;
- store = configureStore(this.jsterm.hud);
+ store = configureStore(this.hud);
}
NewConsoleOutputWrapper.prototype = {
init: function() {
return new Promise((resolve) => {
const attachRefToHud = (id, node) => {
- this.jsterm.hud[id] = node;
+ this.hud[id] = node;
};
// Focus the input line whenever the output area is clicked.
this.parentNode.addEventListener("click", (event) => {
// Do not focus on middle/right-click or 2+ clicks.
if (event.detail !== 1 || event.button !== 0) {
return;
}
@@ -70,25 +71,27 @@ NewConsoleOutputWrapper.prototype = {
}
// Do not focus if something is selected
let selection = this.document.defaultView.getSelection();
if (selection && !selection.isCollapsed) {
return;
}
- this.jsterm.focus();
+ if (this.hud && this.hud.jsterm) {
+ this.hud.jsterm.focus();
+ }
});
- let { hud } = this.jsterm;
+ let { hud } = this;
const serviceContainer = {
attachRefToHud,
emitNewMessage: (node, messageId, timeStamp) => {
- this.jsterm.hud.emit("new-messages", new Set([{
+ hud.emit("new-messages", new Set([{
node,
messageId,
timeStamp,
}]));
},
hudProxy: hud.proxy,
openLink: (url, e) => {
hud.owner.openLink(url, e);
@@ -135,32 +138,38 @@ NewConsoleOutputWrapper.prototype = {
rootObjectInspector.querySelector("[data-link-actor-id]") : null;
let rootActorId = rootActor ? rootActor.dataset.linkActorId : null;
let sidebarTogglePref = store.getState().prefs.sidebarToggle;
let openSidebar = sidebarTogglePref ? (messageId) => {
store.dispatch(actions.showObjectInSidebar(rootActorId, messageId));
} : null;
- let menu = createContextMenu(this.jsterm, this.parentNode,
- { actor, clipboardText, variableText, message,
- serviceContainer, openSidebar, rootActorId });
+ let menu = createContextMenu(this.hud, this.parentNode, {
+ actor,
+ clipboardText,
+ variableText,
+ message,
+ serviceContainer,
+ openSidebar,
+ rootActorId
+ });
// Emit the "menu-open" event for testing.
menu.once("open", () => this.emit("menu-open"));
menu.popup(screenX, screenY, { doc: this.owner.chromeWindow.document });
return menu;
};
if (this.toolbox) {
Object.assign(serviceContainer, {
onViewSourceInDebugger: frame => {
this.toolbox.viewSourceInDebugger(frame.url, frame.line).then(() =>
- this.jsterm.hud.emit("source-in-debugger-opened")
+ this.hud.emit("source-in-debugger-opened")
);
},
onViewSourceInScratchpad: frame => this.toolbox.viewSourceInScratchpad(
frame.url,
frame.line
),
onViewSourceInStyleEditor: frame => this.toolbox.viewSourceInStyleEditor(
frame.url,
@@ -194,70 +203,64 @@ NewConsoleOutputWrapper.prototype = {
let onNodeFrontSet = this.toolbox.selection
.setNodeFront(front, { reason: "console" });
return Promise.all([onNodeFrontSet, onInspectorUpdated]);
}
});
}
- let consoleOutput = ConsoleOutput({
- serviceContainer,
- onFirstMeaningfulPaint: resolve
- });
-
- let filterBar = FilterBar({
- hidePersistLogsCheckbox: this.jsterm.hud.isBrowserConsole,
- serviceContainer: {
- attachRefToHud
- }
- });
-
- let sideBar = SideBar({
- serviceContainer,
- });
-
let provider = createElement(
Provider,
{ store },
dom.div(
{className: "webconsole-output-wrapper"},
- filterBar,
- consoleOutput,
- sideBar
+ FilterBar({
+ hidePersistLogsCheckbox: this.hud.isBrowserConsole,
+ serviceContainer: {
+ attachRefToHud
+ }
+ }),
+ ConsoleOutput({
+ serviceContainer,
+ onFirstMeaningfulPaint: resolve
+ }),
+ SideBar({
+ serviceContainer,
+ }),
+ JSTerm({
+ hud: this.hud,
+ }),
));
this.body = ReactDOM.render(provider, this.parentNode);
-
- this.jsterm.focus();
});
},
dispatchMessageAdd: function(packet, waitForResponse) {
// Wait for the message to render to resolve with the DOM node.
// This is just for backwards compatibility with old tests, and should
// be removed once it's not needed anymore.
// Can only wait for response if the action contains a valid message.
let promise;
// Also, do not expect any update while the panel is in background.
if (waitForResponse && document.visibilityState === "visible") {
const timeStampToMatch = packet.message
? packet.message.timeStamp
: packet.timestamp;
promise = new Promise(resolve => {
- let jsterm = this.jsterm;
- jsterm.hud.on("new-messages", function onThisMessage(messages) {
+ this.hud.on("new-messages", function onThisMessage(messages) {
for (let m of messages) {
if (m.timeStamp === timeStampToMatch) {
resolve(m.node);
- jsterm.hud.off("new-messages", onThisMessage);
+ this.hud.off("new-messages", onThisMessage);
return;
}
}
- });
+ }.bind(this));
});
} else {
promise = Promise.resolve();
}
this.batchedMessagesAdd(packet);
return promise;
},
@@ -383,34 +386,34 @@ NewConsoleOutputWrapper.prototype = {
this.throttledDispatchPromise = null;
store.dispatch(actions.messagesAdd(this.queuedMessageAdds));
this.queuedMessageAdds = [];
if (this.queuedMessageUpdates.length > 0) {
this.queuedMessageUpdates.forEach(({ message, res }) => {
store.dispatch(actions.networkMessageUpdate(message, null, res));
- this.jsterm.hud.emit("network-message-updated", res);
+ this.hud.emit("network-message-updated", res);
});
this.queuedMessageUpdates = [];
}
if (this.queuedRequestUpdates.length > 0) {
this.queuedRequestUpdates.forEach(({ id, data}) => {
store.dispatch(actions.networkUpdateRequest(id, data));
});
this.queuedRequestUpdates = [];
// Fire an event indicating that all data fetched from
// the backend has been received. This is based on
// 'FirefoxDataProvider.isQueuePayloadReady', see more
// comments in that method.
// (netmonitor/src/connector/firefox-data-provider).
// This event might be utilized in tests to find the right
// time when to finish.
- this.jsterm.hud.emit("network-request-payload-ready");
+ this.hud.emit("network-request-payload-ready");
}
done();
}, 50);
});
},
// Should be used for test purpose only.
getStore: function() {