Bug 1472161 - Enter tabs in console when not at an autocomplete location;r=nchevobbe
This also allows Tab / Shift+Tab to unfocus the input if it is empty.
MozReview-Commit-ID: C8u5GTzvA8Z
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -57,20 +57,16 @@ noCounterLabel=<no label>
# LOCALIZATION NOTE (counterDoesntExist): this string is displayed when
# console.countReset() is called with a counter that doesn't exist.
counterDoesntExist=Counter “%S” doesn’t exist.
# LOCALIZATION NOTE (noGroupLabel): this string is used to display
# console.group messages with no label provided.
noGroupLabel=<no group label>
-# LOCALIZATION NOTE (Autocomplete.blank): this string is used when inputnode
-# string containing anchor doesn't matches to any property in the content.
-Autocomplete.blank= <- no result
-
maxTimersExceeded=The maximum allowed number of timers in this page was exceeded.
timerAlreadyExists=Timer “%S” already exists.
timerDoesntExist=Timer “%S” doesn’t exist.
timerJSError=Failed to process the timer name.
# LOCALIZATION NOTE (connectionTimeout): message displayed when the Remote Web
# Console fails to connect to the server due to a timeout.
connectionTimeout=Connection timeout. Check the Error Console on both ends for potential error messages. Reopen the Web Console to try again.
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -92,17 +92,16 @@ class JSTerm extends Component {
/**
* 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;
/**
* Array that caches the user input suggestions received from the server.
* @private
* @type array
@@ -127,24 +126,16 @@ class JSTerm extends Component {
/**
* Last input value.
* @type string
*/
this.lastInputValue = "";
/**
- * Tells if the input node changed since the last focus.
- *
- * @private
- * @type boolean
- */
- this._inputChanged = false;
-
- /**
* Tells if the autocomplete popup was navigated since the last open.
*
* @private
* @type boolean
*/
this._autocompletePopupNavigated = false;
this.autocompletePopup = null;
@@ -282,17 +273,16 @@ class JSTerm extends Component {
const cm = this.editor.codeMirror;
cm.on("paste", (_, event) => this.props.onPaste(event));
cm.on("drop", (_, event) => this.props.onPaste(event));
}
} else if (this.inputNode) {
this.inputNode.addEventListener("keypress", this._keyPress);
this.inputNode.addEventListener("input", this._inputEventHandler);
this.inputNode.addEventListener("keyup", this._inputEventHandler);
- this.inputNode.addEventListener("focus", this._focusEventHandler);
this.focus();
}
this.hud.window.addEventListener("blur", this._blurEventHandler);
this.lastInputValue && this.setInputValue(this.lastInputValue);
}
shouldComponentUpdate(nextProps, nextState) {
@@ -618,17 +608,16 @@ class JSTerm extends Component {
}
this.inputNode.value = newValue;
this.completeNode.value = "";
}
this.lastInputValue = newValue;
this.resizeInput();
- this._inputChanged = true;
this.emit("set-input-value");
}
/**
* Gets the value from the input field
* @returns string
*/
getInputValue() {
@@ -643,17 +632,16 @@ class JSTerm extends Component {
* The inputNode "input" and "keyup" event handler.
* @private
*/
_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() {
@@ -752,17 +740,16 @@ class JSTerm extends Component {
case KeyCodes.DOM_VK_RETURN:
if (this._autocompletePopupNavigated &&
this.autocompletePopup.isOpen &&
this.autocompletePopup.selectedIndex > -1) {
this.acceptProposedCompletion();
} else {
this.execute();
- this._inputChanged = false;
}
event.preventDefault();
break;
case KeyCodes.DOM_VK_UP:
if (this.autocompletePopup.isOpen) {
inputUpdated = this.complete(this.COMPLETE_BACKWARD);
if (inputUpdated) {
@@ -870,36 +857,30 @@ class JSTerm extends Component {
break;
case KeyCodes.DOM_VK_TAB:
// Generate a completion and accept the first proposed value.
if (this.complete(this.COMPLETE_HINT_ONLY) &&
this.lastCompletion &&
this.acceptProposedCompletion()) {
event.preventDefault();
- } else if (this._inputChanged) {
- this.updateCompleteNode(l10n.getStr("Autocomplete.blank"));
+ } else if (!this.hasEmptyInput()) {
+ if (!event.shiftKey) {
+ this.insertStringAtCursor("\t");
+ }
event.preventDefault();
}
break;
default:
break;
}
}
/* eslint-enable complexity */
/**
- * The inputNode "focus" event handler.
- * @private
- */
- _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.
*/
@@ -922,16 +903,25 @@ class JSTerm extends Component {
this.setInputValue(newInputValue);
return true;
}
return false;
}
/**
+ * Test for empty input.
+ *
+ * @return boolean
+ */
+ hasEmptyInput() {
+ return this.getInputValue() === "";
+ }
+
+ /**
* Test for multiline input.
*
* @return boolean
* True if CR or LF found in node value; else false.
*/
hasMultilineInput() {
return /[\r\n]/.test(this.getInputValue());
}
@@ -1274,33 +1264,43 @@ class JSTerm extends Component {
* True if there was a selected completion item and the input value
* was updated, false otherwise.
*/
acceptProposedCompletion() {
let updated = false;
const currentItem = this.autocompletePopup.selectedItem;
if (currentItem && this.lastCompletion.value) {
- const suffix =
- currentItem.label.substring(this.lastCompletion.matchProp.length);
- const cursor = this.inputNode.selectionStart;
- const value = this.getInputValue();
- this.setInputValue(value.substr(0, cursor) +
- suffix + value.substr(cursor));
- const newCursor = cursor + suffix.length;
- this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
+ this.insertStringAtCursor(
+ currentItem.label.substring(this.lastCompletion.matchProp.length)
+ );
updated = true;
}
this.clearCompletion();
return updated;
}
/**
+ * Insert a string into the console at the cursor location,
+ * moving the cursor to the end of the string.
+ *
+ * @param string str
+ */
+ insertStringAtCursor(str) {
+ const cursor = this.inputNode.selectionStart;
+ const value = this.getInputValue();
+ this.setInputValue(value.substr(0, cursor) +
+ str + value.substr(cursor));
+ const newCursor = cursor + str.length;
+ this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
+ }
+
+ /**
* Update the node that displays the currently selected autocomplete proposal.
*
* @param string suffix
* The proposed suffix for the inputNode value.
*/
updateCompleteNode(suffix) {
if (!this.completeNode) {
return;
@@ -1354,17 +1354,16 @@ class JSTerm extends Component {
this.autocompletePopup.destroy();
this.autocompletePopup = null;
}
if (this.inputNode) {
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);
}
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -220,17 +220,16 @@ skip-if = os != 'mac' # The tested ctrl+
[browser_jsterm_history_nav.js]
[browser_jsterm_history_persist.js]
[browser_jsterm_input_expansion.js]
[browser_jsterm_inspect.js]
[browser_jsterm_instance_of.js]
[browser_jsterm_multiline.js]
[browser_jsterm_no_autocompletion_on_defined_variables.js]
[browser_jsterm_no_input_and_tab_key_pressed.js]
-[browser_jsterm_no_input_change_and_tab_key_pressed.js]
[browser_jsterm_null_undefined.js]
[browser_jsterm_popup_close_on_tab_switch.js]
[browser_jsterm_screenshot_command_clipboard.js]
subsuite = clipboard
[browser_jsterm_screenshot_command_file.js]
skip-if = (os == 'win') # Bug 1464461, disabled on Win due to timeouts
[browser_jsterm_selfxss.js]
subsuite = clipboard
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
@@ -13,22 +13,22 @@ add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
testCompletion(hud);
});
function testCompletion(hud) {
const jsterm = hud.jsterm;
const input = jsterm.inputNode;
+ // With empty input, tab through
jsterm.setInputValue("");
EventUtils.synthesizeKey("KEY_Tab");
- is(jsterm.completeNode.value, "<- no result", "<- no result - matched");
- is(input.value, "", "inputnode is empty - matched");
- ok(hasFocus(input), "input is still focused");
+ is(jsterm.getInputValue(), "", "inputnode is empty - matched");
+ ok(!hasFocus(input), "input isn't focused anymore");
+ jsterm.focus();
- // Any thing which is not in property autocompleter
+ // With non-empty input, insert a tab
jsterm.setInputValue("window.Bug583816");
EventUtils.synthesizeKey("KEY_Tab");
- is(jsterm.completeNode.value, " <- no result",
- "completenode content - matched");
- is(input.value, "window.Bug583816", "inputnode content - matched");
+ is(jsterm.getInputValue(), "window.Bug583816\t",
+ "input content - matched");
ok(hasFocus(input), "input is still focused");
}
deleted file mode 100644
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_change_and_tab_key_pressed.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// See Bug 734061.
-
-const TEST_URI = "data:text/html,Testing jsterm focus";
-
-add_task(async function() {
- const hud = await openNewTabAndConsole(TEST_URI);
- const jsterm = hud.jsterm;
- const input = jsterm.inputNode;
-
- is(hasFocus(input), true, "input has focus");
- EventUtils.synthesizeKey("KEY_Tab");
- is(hasFocus(input), false, "focus moved away");
-
- // Test user changed something
- input.focus();
- EventUtils.sendString("A");
- EventUtils.synthesizeKey("KEY_Tab");
- is(hasFocus(input), true, "input is still focused");
-
- // Test non empty input but not changed since last focus
- input.blur();
- input.focus();
- EventUtils.synthesizeKey("KEY_ArrowRight");
- EventUtils.synthesizeKey("KEY_Tab");
- is(hasFocus(input), false, "focus moved away");
-});