mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-04 07:40:42 +00:00
Bug 1462390 - Extract history from JSTerm component; r=nchevobbe
MozReview-Commit-ID: DTlW1h2ACoI --HG-- extra : rebase_source : 90ecbeee7d198c779c8f19986722dcfd8970fad9
This commit is contained in:
parent
bce82ccf82
commit
0f7d120ed3
@ -45,6 +45,6 @@ add_task(async function() {
|
||||
isnot(result.textContent.indexOf('<p id="console-var-multi">'), -1,
|
||||
"variable temp1 references correct node");
|
||||
|
||||
jsterm.clearHistory();
|
||||
hud.ui.consoleOutput.dispatchClearHistory();
|
||||
}
|
||||
});
|
||||
|
@ -3,6 +3,10 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
getHistoryEntries,
|
||||
} = require("devtools/client/webconsole/selectors/history");
|
||||
|
||||
// Tests for menuitem functionality that doesn't fit into any specific category
|
||||
const TEST_URL = URL_ROOT + "doc_inspector_menu.html";
|
||||
add_task(async function() {
|
||||
@ -30,7 +34,9 @@ add_task(async function() {
|
||||
let messagesAdded = webconsoleUI.once("new-messages");
|
||||
await messagesAdded;
|
||||
info("Checking if 'inspect($0)' was evaluated");
|
||||
ok(webconsoleUI.jsterm.history[0] === "inspect($0)");
|
||||
|
||||
let state = webconsoleUI.consoleOutput.getStore().getState();
|
||||
ok(getHistoryEntries(state)[0] === "inspect($0)");
|
||||
await toolbox.toggleSplitConsole();
|
||||
}
|
||||
async function testDuplicateNode() {
|
||||
|
65
devtools/client/webconsole/actions/history.js
Normal file
65
devtools/client/webconsole/actions/history.js
Normal file
@ -0,0 +1,65 @@
|
||||
/* -*- 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 {
|
||||
APPEND_TO_HISTORY,
|
||||
CLEAR_HISTORY,
|
||||
HISTORY_LOADED,
|
||||
UPDATE_HISTORY_PLACEHOLDER,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
|
||||
/**
|
||||
* Append a new value in the history of executed expressions,
|
||||
* or overwrite the most recent entry. The most recent entry may
|
||||
* contain the last edited input value that was not evaluated yet.
|
||||
*/
|
||||
function appendToHistory(expression) {
|
||||
return {
|
||||
type: APPEND_TO_HISTORY,
|
||||
expression: expression,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function clearHistory() {
|
||||
return {
|
||||
type: CLEAR_HISTORY,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the console history from previous Firefox sessions is loaded.
|
||||
*/
|
||||
function historyLoaded(entries) {
|
||||
return {
|
||||
type: HISTORY_LOADED,
|
||||
entries,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update place-holder position in the history list.
|
||||
*/
|
||||
function updatePlaceHolder(direction, expression) {
|
||||
return {
|
||||
type: UPDATE_HISTORY_PLACEHOLDER,
|
||||
direction,
|
||||
expression,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
appendToHistory,
|
||||
clearHistory,
|
||||
historyLoaded,
|
||||
updatePlaceHolder,
|
||||
};
|
@ -11,6 +11,7 @@ const actionModules = [
|
||||
require("./messages"),
|
||||
require("./ui"),
|
||||
require("./notifications"),
|
||||
require("./history"),
|
||||
];
|
||||
|
||||
const actions = Object.assign({}, ...actionModules);
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
DevToolsModules(
|
||||
'filters.js',
|
||||
'history.js',
|
||||
'index.js',
|
||||
'messages.js',
|
||||
'notifications.js',
|
||||
|
@ -14,7 +14,6 @@ loader.lazyRequireGetter(this, "defer", "devtools/shared/defer");
|
||||
loader.lazyRequireGetter(this, "Debugger", "Debugger");
|
||||
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
||||
loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
|
||||
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
|
||||
loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
|
||||
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
|
||||
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
|
||||
@ -22,13 +21,7 @@ loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
|
||||
|
||||
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 HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
|
||||
|
||||
const PREF_INPUT_HISTORY_COUNT = "devtools.webconsole.inputHistoryCount";
|
||||
const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
|
||||
|
||||
function gSequenceId() {
|
||||
@ -36,8 +29,23 @@ function gSequenceId() {
|
||||
}
|
||||
gSequenceId.n = 0;
|
||||
|
||||
// React & Redux
|
||||
const { Component } = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
// History Modules
|
||||
const {
|
||||
getHistory,
|
||||
getHistoryValue
|
||||
} = require("devtools/client/webconsole/selectors/history");
|
||||
const historyActions = require("devtools/client/webconsole/actions/history");
|
||||
|
||||
// Constants used for defining the direction of JSTerm input history navigation.
|
||||
const {
|
||||
HISTORY_BACK,
|
||||
HISTORY_FORWARD
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
|
||||
/**
|
||||
* Create a JSTerminal (a JavaScript command line). This is attached to an
|
||||
@ -51,10 +59,22 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
class JSTerm extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
// Append new executed expression into history list (action).
|
||||
appendToHistory: PropTypes.func.isRequired,
|
||||
// Remove all entries from the history list (action).
|
||||
clearHistory: PropTypes.func.isRequired,
|
||||
// Returns previous or next value from the history
|
||||
// (depending on direction argument).
|
||||
getValueFromHistory: PropTypes.func.isRequired,
|
||||
// History of executed expression (state).
|
||||
history: PropTypes.object.isRequired,
|
||||
// Console object.
|
||||
hud: PropTypes.object.isRequired,
|
||||
// Handler for clipboard 'paste' event (also used for 'drop' event).
|
||||
// Handler for clipboard 'paste' event (also used for 'drop' event, callback).
|
||||
onPaste: PropTypes.func,
|
||||
codeMirrorEnabled: PropTypes.bool,
|
||||
// Update position in the history after executing an expression (action).
|
||||
updatePlaceHolder: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
@ -67,8 +87,6 @@ class JSTerm extends Component {
|
||||
|
||||
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.
|
||||
@ -128,11 +146,6 @@ class JSTerm extends Component {
|
||||
*/
|
||||
this._autocompletePopupNavigated = false;
|
||||
|
||||
/**
|
||||
* History of code that was executed.
|
||||
* @type array
|
||||
*/
|
||||
this.history = [];
|
||||
this.autocompletePopup = null;
|
||||
this.inputNode = null;
|
||||
this.completeNode = null;
|
||||
@ -214,62 +227,13 @@ class JSTerm extends Component {
|
||||
this.lastInputValue && this.setInputValue(this.lastInputValue);
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
// XXX: For now, everything is handled in an imperative way and we only want React
|
||||
// to do the initial rendering of the component.
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
// 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() {
|
||||
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.
|
||||
this.history = value.concat(this.history);
|
||||
|
||||
// Holds the number of entries in history. This value is incremented
|
||||
// in this.execute().
|
||||
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() {
|
||||
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() {
|
||||
return asyncStorage.setItem("webConsoleHistory", this.history);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the element that holds the messages we display.
|
||||
* @type Element
|
||||
@ -328,7 +292,7 @@ class JSTerm extends Component {
|
||||
this.clearOutput();
|
||||
break;
|
||||
case "clearHistory":
|
||||
this.clearHistory();
|
||||
this.props.clearHistory();
|
||||
break;
|
||||
case "inspectObject":
|
||||
this.inspectObjectActor(helperResult.object);
|
||||
@ -394,17 +358,9 @@ class JSTerm extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Append a new value in the history of executed code, or overwrite the most
|
||||
// recent entry. The most recent entry may contain the last edited input
|
||||
// value that was not evaluated yet.
|
||||
this.history[this.historyIndex++] = executeString;
|
||||
this.historyPlaceHolder = this.history.length;
|
||||
// Append executed expression into the history list.
|
||||
this.props.appendToHistory(executeString);
|
||||
|
||||
if (this.history.length > this.inputHistoryCount) {
|
||||
this.history.splice(0, this.history.length - this.inputHistoryCount);
|
||||
this.historyIndex = this.historyPlaceHolder = this.history.length;
|
||||
}
|
||||
this.storeHistory();
|
||||
WebConsoleUtils.usageCount++;
|
||||
this.setInputValue("");
|
||||
this.clearCompletion();
|
||||
@ -892,39 +848,26 @@ class JSTerm extends Component {
|
||||
* True if the input value changed, false otherwise.
|
||||
*/
|
||||
historyPeruse(direction) {
|
||||
if (!this.history.length) {
|
||||
let {
|
||||
history,
|
||||
updatePlaceHolder,
|
||||
getValueFromHistory,
|
||||
} = this.props;
|
||||
|
||||
if (!history.entries.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Up Arrow key
|
||||
if (direction == HISTORY_BACK) {
|
||||
if (this.historyPlaceHolder <= 0) {
|
||||
return false;
|
||||
}
|
||||
let inputVal = this.history[--this.historyPlaceHolder];
|
||||
let newInputValue = getValueFromHistory(direction);
|
||||
let expression = this.getInputValue();
|
||||
updatePlaceHolder(direction, expression);
|
||||
|
||||
// Save the current input value as the latest entry in history, only if
|
||||
// the user is already at the last entry.
|
||||
// Note: this code does not store changes to items that are already in
|
||||
// history.
|
||||
if (this.historyPlaceHolder + 1 == this.historyIndex) {
|
||||
this.history[this.historyIndex] = this.getInputValue() || "";
|
||||
}
|
||||
|
||||
this.setInputValue(inputVal);
|
||||
} else if (direction == HISTORY_FORWARD) {
|
||||
// Down Arrow key
|
||||
if (this.historyPlaceHolder >= (this.history.length - 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let inputVal = this.history[++this.historyPlaceHolder];
|
||||
this.setInputValue(inputVal);
|
||||
} else {
|
||||
throw new Error("Invalid argument 0");
|
||||
if (newInputValue != null) {
|
||||
this.setInputValue(newInputValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1404,4 +1347,22 @@ class JSTerm extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JSTerm;
|
||||
// Redux connect
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
history: getHistory(state),
|
||||
getValueFromHistory: (direction) => getHistoryValue(state, direction),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
appendToHistory: (expr) => dispatch(historyActions.appendToHistory(expr)),
|
||||
clearHistory: () => dispatch(historyActions.clearHistory()),
|
||||
updatePlaceHolder: (direction, expression) =>
|
||||
dispatch(historyActions.updatePlaceHolder(direction, expression)),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(JSTerm);
|
||||
|
@ -30,6 +30,10 @@ const actionTypes = {
|
||||
APPEND_NOTIFICATION: "APPEND_NOTIFICATION",
|
||||
REMOVE_NOTIFICATION: "REMOVE_NOTIFICATION",
|
||||
SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE: "SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE",
|
||||
APPEND_TO_HISTORY: "APPEND_TO_HISTORY",
|
||||
CLEAR_HISTORY: "CLEAR_HISTORY",
|
||||
HISTORY_LOADED: "HISTORY_LOADED",
|
||||
UPDATE_HISTORY_PLACEHOLDER: "UPDATE_HISTORY_PLACEHOLDER",
|
||||
};
|
||||
|
||||
const prefs = {
|
||||
@ -52,6 +56,8 @@ const prefs = {
|
||||
FILTER_BAR: "ui.filterbar",
|
||||
// Persist is only used by the webconsole.
|
||||
PERSIST: "devtools.webconsole.persistlog",
|
||||
// Max number of entries in history list.
|
||||
INPUT_HISTORY_COUNT: "devtools.webconsole.inputHistoryCount",
|
||||
},
|
||||
FEATURES: {
|
||||
// We use the same pref to enable the sidebar on webconsole and browser console.
|
||||
@ -138,6 +144,12 @@ const jstermCommands = {
|
||||
}
|
||||
};
|
||||
|
||||
// Constants used for defining the direction of JSTerm input history navigation.
|
||||
const historyCommands = {
|
||||
HISTORY_BACK: -1,
|
||||
HISTORY_FORWARD: 1,
|
||||
};
|
||||
|
||||
// Combine into a single constants object
|
||||
module.exports = Object.assign({
|
||||
FILTERS,
|
||||
@ -148,4 +160,5 @@ module.exports = Object.assign({
|
||||
chromeRDPEnums,
|
||||
jstermCommands,
|
||||
prefs,
|
||||
historyCommands,
|
||||
);
|
||||
|
126
devtools/client/webconsole/reducers/history.js
Normal file
126
devtools/client/webconsole/reducers/history.js
Normal file
@ -0,0 +1,126 @@
|
||||
/* -*- 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 {
|
||||
APPEND_TO_HISTORY,
|
||||
CLEAR_HISTORY,
|
||||
HISTORY_LOADED,
|
||||
UPDATE_HISTORY_PLACEHOLDER,
|
||||
HISTORY_BACK,
|
||||
HISTORY_FORWARD,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
|
||||
/**
|
||||
* Create default initial state for this reducer.
|
||||
*/
|
||||
function getInitialState() {
|
||||
return {
|
||||
// Array with history entries
|
||||
entries: [],
|
||||
|
||||
// Holds the index of the history entry that the user is currently
|
||||
// viewing. This is reset to this.history.length when APPEND_TO_HISTORY
|
||||
// action is fired.
|
||||
placeHolder: undefined,
|
||||
|
||||
// Holds the number of entries in history. This value is incremented
|
||||
// when APPEND_TO_HISTORY action is fired and used to get previous
|
||||
// value from the command line when the user goes backward.
|
||||
index: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function history(state = getInitialState(), action, prefsState) {
|
||||
switch (action.type) {
|
||||
case APPEND_TO_HISTORY:
|
||||
return appendToHistory(state, prefsState, action.expression);
|
||||
case CLEAR_HISTORY:
|
||||
return clearHistory(state);
|
||||
case HISTORY_LOADED:
|
||||
return historyLoaded(state, action.entries);
|
||||
case UPDATE_HISTORY_PLACEHOLDER:
|
||||
return updatePlaceHolder(state, action.direction, action.expression);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function appendToHistory(state, prefsState, expression) {
|
||||
// Clone state
|
||||
state = {...state};
|
||||
state.entries = [...state.entries];
|
||||
|
||||
// Append new expression
|
||||
state.entries[state.index++] = expression;
|
||||
state.placeHolder = state.entries.length;
|
||||
|
||||
// Remove entries if the limit is reached
|
||||
if (state.entries.length > prefsState.historyCount) {
|
||||
state.entries.splice(0, state.entries.length - prefsState.historyCount);
|
||||
state.index = state.placeHolder = state.entries.length;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function clearHistory(state) {
|
||||
return getInitialState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handling HISTORY_LOADED action that is fired when history
|
||||
* entries created in previous Firefox session are loaded
|
||||
* from async-storage.
|
||||
*
|
||||
* Loaded entries are appended before the ones that were
|
||||
* added to the state in this session.
|
||||
*/
|
||||
function historyLoaded(state, entries) {
|
||||
let newEntries = [...entries, ...state.entries];
|
||||
return {
|
||||
...state,
|
||||
entries: newEntries,
|
||||
placeHolder: newEntries.length,
|
||||
index: newEntries.length,
|
||||
};
|
||||
}
|
||||
|
||||
function updatePlaceHolder(state, direction, expression) {
|
||||
// Handle UP arrow key => HISTORY_BACK
|
||||
// Handle DOWN arrow key => HISTORY_FORWARD
|
||||
if (direction == HISTORY_BACK) {
|
||||
if (state.placeHolder <= 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Clone state
|
||||
state = {...state};
|
||||
|
||||
// Save the current input value as the latest entry in history, only if
|
||||
// the user is already at the last entry.
|
||||
// Note: this code does not store changes to items that are already in
|
||||
// history.
|
||||
if (state.placeHolder == state.index) {
|
||||
state.entries = [...state.entries];
|
||||
state.entries[state.index] = expression || "";
|
||||
}
|
||||
|
||||
state.placeHolder--;
|
||||
} else if (direction == HISTORY_FORWARD) {
|
||||
if (state.placeHolder >= (state.entries.length - 1)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
state = {
|
||||
...state,
|
||||
placeHolder: state.placeHolder + 1,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
exports.history = history;
|
@ -10,6 +10,7 @@ const { messages } = require("./messages");
|
||||
const { prefs } = require("./prefs");
|
||||
const { ui } = require("./ui");
|
||||
const { notifications } = require("./notifications");
|
||||
const { history } = require("./history");
|
||||
|
||||
exports.reducers = {
|
||||
filters,
|
||||
@ -17,4 +18,5 @@ exports.reducers = {
|
||||
prefs,
|
||||
ui,
|
||||
notifications,
|
||||
history,
|
||||
};
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
DevToolsModules(
|
||||
'filters.js',
|
||||
'history.js',
|
||||
'index.js',
|
||||
'messages.js',
|
||||
'notifications.js',
|
||||
|
@ -9,6 +9,7 @@ const PrefState = (overrides) => Object.freeze(Object.assign({
|
||||
logLimit: 1000,
|
||||
sidebarToggle: false,
|
||||
jstermCodeMirror: false,
|
||||
historyCount: 50,
|
||||
}, overrides));
|
||||
|
||||
function prefs(state = PrefState(), action) {
|
||||
|
50
devtools/client/webconsole/selectors/history.js
Normal file
50
devtools/client/webconsole/selectors/history.js
Normal file
@ -0,0 +1,50 @@
|
||||
/* -*- 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 {
|
||||
HISTORY_BACK,
|
||||
HISTORY_FORWARD,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
|
||||
function getHistory(state) {
|
||||
return state.history;
|
||||
}
|
||||
|
||||
function getHistoryEntries(state) {
|
||||
return state.history.entries;
|
||||
}
|
||||
|
||||
function getHistoryValue(state, direction) {
|
||||
if (direction == HISTORY_BACK) {
|
||||
return getPreviousHistoryValue(state);
|
||||
}
|
||||
if (direction == HISTORY_FORWARD) {
|
||||
return getNextHistoryValue(state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getNextHistoryValue(state) {
|
||||
if (state.history.placeHolder < (state.history.entries.length - 1)) {
|
||||
return state.history.entries[state.history.placeHolder + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPreviousHistoryValue(state) {
|
||||
if (state.history.placeHolder > 0) {
|
||||
return state.history.entries[state.history.placeHolder - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getHistory,
|
||||
getHistoryEntries,
|
||||
getHistoryValue,
|
||||
};
|
@ -5,6 +5,7 @@
|
||||
|
||||
DevToolsModules(
|
||||
'filters.js',
|
||||
'history.js',
|
||||
'messages.js',
|
||||
'notifications.js',
|
||||
'prefs.js',
|
||||
|
@ -24,6 +24,8 @@ const {
|
||||
PREFS,
|
||||
INITIALIZE,
|
||||
FILTER_TOGGLE,
|
||||
APPEND_TO_HISTORY,
|
||||
CLEAR_HISTORY,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
const { reducers } = require("./reducers/index");
|
||||
const {
|
||||
@ -35,6 +37,9 @@ const {
|
||||
getAllNetworkMessagesUpdateById,
|
||||
} = require("devtools/client/webconsole/selectors/messages");
|
||||
const {getPrefsService} = require("devtools/client/webconsole/utils/prefs");
|
||||
const historyActions = require("devtools/client/webconsole/actions/history");
|
||||
|
||||
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
|
||||
|
||||
/**
|
||||
* Create and configure store for the Console panel. This is the place
|
||||
@ -51,12 +56,14 @@ function configureStore(hud, options = {}) {
|
||||
|| Math.max(getIntPref("devtools.hud.loglimit"), 1);
|
||||
const sidebarToggle = getBoolPref(PREFS.FEATURES.SIDEBAR_TOGGLE);
|
||||
const jstermCodeMirror = getBoolPref(PREFS.FEATURES.JSTERM_CODE_MIRROR);
|
||||
const historyCount = getIntPref(PREFS.UI.INPUT_HISTORY_COUNT);
|
||||
|
||||
const initialState = {
|
||||
prefs: PrefState({
|
||||
logLimit,
|
||||
sidebarToggle,
|
||||
jstermCodeMirror,
|
||||
historyCount,
|
||||
}),
|
||||
filters: FilterState({
|
||||
error: getBoolPref(PREFS.FILTER.ERROR),
|
||||
@ -75,11 +82,17 @@ function configureStore(hud, options = {}) {
|
||||
})
|
||||
};
|
||||
|
||||
// Prepare middleware.
|
||||
let middleware = applyMiddleware(
|
||||
thunk.bind(null, {prefsService}),
|
||||
historyPersistenceMiddleware,
|
||||
);
|
||||
|
||||
return createStore(
|
||||
createRootReducer(),
|
||||
initialState,
|
||||
compose(
|
||||
applyMiddleware(thunk.bind(null, {prefsService})),
|
||||
middleware,
|
||||
enableActorReleaser(hud),
|
||||
enableBatching(),
|
||||
enableNetProvider(hud),
|
||||
@ -99,14 +112,19 @@ function thunk(options = {}, { dispatch, getState }) {
|
||||
|
||||
function createRootReducer() {
|
||||
return function rootReducer(state, action) {
|
||||
// We want to compute the new state for all properties except "messages".
|
||||
// We want to compute the new state for all properties except
|
||||
// "messages" and "history". These two reducers are handled
|
||||
// separately since they are receiving additional arguments.
|
||||
const newState = [...Object.entries(reducers)].reduce((res, [key, reducer]) => {
|
||||
if (key !== "messages") {
|
||||
if (key !== "messages" && key !== "history") {
|
||||
res[key] = reducer(state[key], action);
|
||||
}
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
// Pass prefs state as additional argument to the history reducer.
|
||||
newState.history = reducers.history(state.history, action, newState.prefs);
|
||||
|
||||
return Object.assign(newState, {
|
||||
// specifically pass the updated filters and prefs state as additional arguments.
|
||||
messages: reducers.messages(
|
||||
@ -227,7 +245,7 @@ function enableNetProvider(hud) {
|
||||
// Data provider implements async logic for fetching
|
||||
// data from the backend. It's created the first
|
||||
// time it's needed.
|
||||
if (!dataProvider) {
|
||||
if (!dataProvider && proxy.webConsoleClient) {
|
||||
dataProvider = new DataProvider({
|
||||
actions,
|
||||
webConsoleClient: proxy.webConsoleClient
|
||||
@ -323,6 +341,41 @@ function releaseActors(removedActors, proxy) {
|
||||
removedActors.forEach(actor => proxy.releaseActor(actor));
|
||||
}
|
||||
|
||||
/**
|
||||
* History persistence middleware is responsible for loading
|
||||
* and maintaining history of executed expressions in JSTerm.
|
||||
*/
|
||||
function historyPersistenceMiddleware(store) {
|
||||
let historyLoaded = false;
|
||||
asyncStorage.getItem("webConsoleHistory").then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
store.dispatch(historyActions.historyLoaded(value));
|
||||
}
|
||||
historyLoaded = true;
|
||||
}, err => {
|
||||
historyLoaded = true;
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
return next => action => {
|
||||
const res = next(action);
|
||||
|
||||
let triggerStoreActions = [
|
||||
APPEND_TO_HISTORY,
|
||||
CLEAR_HISTORY,
|
||||
];
|
||||
|
||||
// Save the current history entries when modified, but wait till
|
||||
// entries from the previous session are loaded.
|
||||
if (historyLoaded && triggerStoreActions.includes(action.type)) {
|
||||
const state = store.getState();
|
||||
asyncStorage.setItem("webConsoleHistory", state.history.entries);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
// Provide the store factory for test code so that each test is working with
|
||||
// its own instance.
|
||||
module.exports.configureStore = configureStore;
|
||||
|
@ -16,8 +16,16 @@ const TEST_URI = `data:text/html;charset=utf-8,
|
||||
</head>
|
||||
<body>bug 873250 - test pressing return with open popup, but no selection</body>`;
|
||||
|
||||
const {
|
||||
getHistoryEntries,
|
||||
} = require("devtools/client/webconsole/selectors/history");
|
||||
|
||||
add_task(async function() {
|
||||
let { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
const {
|
||||
jsterm,
|
||||
ui,
|
||||
} = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
const {
|
||||
autocompletePopup: popup,
|
||||
completeNode,
|
||||
@ -43,6 +51,9 @@ add_task(async function() {
|
||||
ok(!popup.isOpen, "popup is not open after KEY_Enter");
|
||||
is(jsterm.getInputValue(), "", "inputNode is empty after KEY_Enter");
|
||||
is(completeNode.value, "", "completeNode is empty");
|
||||
is(jsterm.history[jsterm.history.length - 1], "window.testBug",
|
||||
|
||||
const state = ui.consoleOutput.getStore().getState();
|
||||
const entries = getHistoryEntries(state);
|
||||
is(entries[entries.length - 1], "window.testBug",
|
||||
"jsterm history is correct");
|
||||
});
|
||||
|
@ -8,10 +8,13 @@
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "data:text/html;charset=UTF-8,test";
|
||||
const HISTORY_BACK = -1;
|
||||
const HISTORY_FORWARD = 1;
|
||||
const COMMANDS = ["document", "window", "window.location"];
|
||||
|
||||
const {
|
||||
HISTORY_BACK,
|
||||
HISTORY_FORWARD,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
|
||||
add_task(async function() {
|
||||
const { jsterm } = await openNewTabAndConsole(TEST_URI);
|
||||
const { inputNode } = jsterm;
|
||||
|
@ -14,6 +14,10 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
|
||||
"persisting history - bug 943306";
|
||||
const INPUT_HISTORY_COUNT = 10;
|
||||
|
||||
const {
|
||||
getHistoryEntries,
|
||||
} = require("devtools/client/webconsole/selectors/history");
|
||||
|
||||
add_task(async function() {
|
||||
info("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
|
||||
Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount", INPUT_HISTORY_COUNT);
|
||||
@ -21,28 +25,37 @@ add_task(async function() {
|
||||
// First tab: run a bunch of commands and then make sure that you can
|
||||
// navigate through their history.
|
||||
let hud1 = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
is(JSON.stringify(hud1.jsterm.history), "[]", "No history on first tab initially");
|
||||
let state1 = hud1.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state1)),
|
||||
"[]",
|
||||
"No history on first tab initially");
|
||||
await populateInputHistory(hud1);
|
||||
is(JSON.stringify(hud1.jsterm.history),
|
||||
|
||||
state1 = hud1.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state1)),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"First tab has populated history");
|
||||
|
||||
// Second tab: Just make sure that you can navigate through the history
|
||||
// generated by the first tab.
|
||||
let hud2 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud2.jsterm.history),
|
||||
let state2 = hud2.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state2)),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"Second tab has populated history");
|
||||
await testNavigatingHistoryInUI(hud2);
|
||||
is(JSON.stringify(hud2.jsterm.history),
|
||||
|
||||
state2 = hud2.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state2)),
|
||||
'["0","1","2","3","4","5","6","7","8","9",""]',
|
||||
"An empty entry has been added in the second tab due to history perusal");
|
||||
|
||||
// Third tab: Should have the same history as first tab, but if we run a
|
||||
// command, then the history of the first and second shouldn't be affected
|
||||
let hud3 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud3.jsterm.history),
|
||||
let state3 = hud3.ui.consoleOutput.getStore().getState();
|
||||
|
||||
is(JSON.stringify(getHistoryEntries(state3)),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"Third tab has populated history");
|
||||
|
||||
@ -51,13 +64,18 @@ add_task(async function() {
|
||||
hud3.jsterm.setInputValue('"hello from third tab"');
|
||||
await hud3.jsterm.execute();
|
||||
|
||||
is(JSON.stringify(hud1.jsterm.history),
|
||||
state1 = hud1.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state1)),
|
||||
'["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"First tab history hasn't changed due to command in third tab");
|
||||
is(JSON.stringify(hud2.jsterm.history),
|
||||
|
||||
state2 = hud2.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state2)),
|
||||
'["0","1","2","3","4","5","6","7","8","9",""]',
|
||||
"Second tab history hasn't changed due to command in third tab");
|
||||
is(JSON.stringify(hud3.jsterm.history),
|
||||
|
||||
state3 = hud3.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state3)),
|
||||
'["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
|
||||
"Third tab has updated history (and purged the first result) after " +
|
||||
"running a command");
|
||||
@ -65,15 +83,20 @@ add_task(async function() {
|
||||
// Fourth tab: Should have the latest command from the third tab, followed
|
||||
// by the rest of the history from the first tab.
|
||||
let hud4 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud4.jsterm.history),
|
||||
let state4 = hud4.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state4)),
|
||||
'["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
|
||||
"Fourth tab has most recent history");
|
||||
|
||||
await hud4.jsterm.clearHistory();
|
||||
is(JSON.stringify(hud4.jsterm.history), "[]", "Clearing history for a tab works");
|
||||
await hud4.jsterm.props.clearHistory();
|
||||
state4 = hud4.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state4)),
|
||||
"[]",
|
||||
"Clearing history for a tab works");
|
||||
|
||||
let hud5 = await openNewTabAndConsole(TEST_URI, false);
|
||||
is(JSON.stringify(hud5.jsterm.history), "[]",
|
||||
let state5 = hud5.ui.consoleOutput.getStore().getState();
|
||||
is(JSON.stringify(getHistoryEntries(state5)), "[]",
|
||||
"Clearing history carries over to a new tab");
|
||||
|
||||
info("Clearing custom input history pref");
|
||||
|
@ -70,7 +70,7 @@ async function openNewTabAndConsole(url, clearJstermHistory = true) {
|
||||
|
||||
if (clearJstermHistory) {
|
||||
// Clearing history that might have been set in previous tests.
|
||||
await hud.jsterm.clearHistory();
|
||||
await hud.ui.consoleOutput.dispatchClearHistory();
|
||||
}
|
||||
|
||||
return hud;
|
||||
|
@ -367,6 +367,10 @@ WebConsoleOutputWrapper.prototype = {
|
||||
this.setTimeoutIfNeeded();
|
||||
},
|
||||
|
||||
dispatchClearHistory: function() {
|
||||
store.dispatch(actions.clearHistory());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves once any async dispatch is finally dispatched.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user