diff --git a/devtools/client/webconsole/reducers/autocomplete.js b/devtools/client/webconsole/reducers/autocomplete.js index fa66182b2135..185004ea1fe7 100644 --- a/devtools/client/webconsole/reducers/autocomplete.js +++ b/devtools/client/webconsole/reducers/autocomplete.js @@ -37,6 +37,10 @@ function autocomplete(state = getDefaultState(), action) { return state; } + if (action.data.matches === null) { + return getDefaultState(); + } + return { ...state, cache: { diff --git a/devtools/client/webconsole/test/mochitest/browser.ini b/devtools/client/webconsole/test/mochitest/browser.ini index 0216cb386347..d23928ef1ca9 100644 --- a/devtools/client/webconsole/test/mochitest/browser.ini +++ b/devtools/client/webconsole/test/mochitest/browser.ini @@ -188,6 +188,7 @@ skip-if = verify [browser_jsterm_autocomplete_accept_no_scroll.js] [browser_jsterm_autocomplete_array_no_index.js] [browser_jsterm_autocomplete_arrow_keys.js] +[browser_jsterm_autocomplete_await.js] [browser_jsterm_autocomplete_cached_results.js] [browser_jsterm_autocomplete_commands.js] [browser_jsterm_autocomplete_control_space.js] diff --git a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_await.js b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_await.js new file mode 100644 index 000000000000..af69e90da209 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_await.js @@ -0,0 +1,44 @@ +/* -*- 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 585991. + +const TEST_URI = `data:text/html;charset=utf-8,Autocomplete await expression`; + +add_task(async function() { + // Run test with legacy JsTerm + await pushPref("devtools.webconsole.jsterm.codeMirror", false); + await performTests(); + // And then run it with the CodeMirror-powered one. + await pushPref("devtools.webconsole.jsterm.codeMirror", true); + await performTests(); +}); + +async function performTests() { + const { jsterm } = await openNewTabAndConsole(TEST_URI); + const { autocompletePopup } = jsterm; + + info("Check that the await keyword is in the autocomplete"); + await setInputValueForAutocompletion(jsterm, "aw"); + checkJsTermCompletionValue(jsterm, " ait", "completeNode has expected value"); + + EventUtils.synthesizeKey("KEY_Tab"); + is(jsterm.getInputValue(), "await", "'await' tab completion"); + + const updated = jsterm.once("autocomplete-updated"); + EventUtils.sendString(" "); + await updated; + + info("Check that the autocomplete popup is displayed"); + const onPopUpOpen = autocompletePopup.once("popup-opened"); + EventUtils.sendString("P"); + await onPopUpOpen; + + ok(autocompletePopup.isOpen, "popup is open"); + ok(autocompletePopup.items.some(item => item.label === "Promise"), + "popup has expected `Promise` item"); +} diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index 129d5afac3e6..a50a8f5cb2af 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -1217,12 +1217,19 @@ WebConsoleActor.prototype = invokeUnsafeGetter: false, webconsoleActor: this, selectedNodeActor: request.selectedNodeActor, - }) || {}; + }); if (!hadDebuggee && dbgObject) { this.dbg.removeDebuggee(this.evalWindow); } + if (result === null) { + return { + from: this.actorID, + matches: null, + }; + } + matches = result.matches || new Set(); matchProp = result.matchProp; isElementAccess = result.isElementAccess; diff --git a/devtools/shared/webconsole/js-property-provider.js b/devtools/shared/webconsole/js-property-provider.js index 77229a7b841b..6ecea3c70f40 100644 --- a/devtools/shared/webconsole/js-property-provider.js +++ b/devtools/shared/webconsole/js-property-provider.js @@ -113,11 +113,6 @@ function analyzeInputString(str) { const nextNonSpaceCharIndex = after.indexOf(nextNonSpaceChar); const previousNonSpaceChar = trimmedBefore[trimmedBefore.length - 1]; - // There's only spaces after that, so we can return. - if (!nextNonSpaceChar) { - return buildReturnObject(); - } - // If the previous char isn't a dot or opening bracket, and the next one isn't // one either, and the current computed statement is not a // variable/function/class declaration, update the start position. @@ -126,7 +121,16 @@ function analyzeInputString(str) { previousNonSpaceChar !== "[" && nextNonSpaceChar !== "[" && !NO_AUTOCOMPLETE_PREFIXES.includes(currentLastStatement) ) { - start = i + nextNonSpaceCharIndex; + start = i + ( + nextNonSpaceCharIndex >= 0 + ? nextNonSpaceCharIndex + : (after.length + 1) + ); + } + + // There's only spaces after that, so we can return. + if (!nextNonSpaceChar) { + return buildReturnObject(); } // Let's jump to handle the next non-space char. diff --git a/devtools/shared/webconsole/test/test_jsterm_autocomplete.html b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html index 6274920e95ca..450ba6e42f5c 100644 --- a/devtools/shared/webconsole/test/test_jsterm_autocomplete.html +++ b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html @@ -17,6 +17,8 @@ MAX_AUTOCOMPLETE_ATTEMPTS, MAX_AUTOCOMPLETIONS } = require("devtools/shared/webconsole/js-property-provider"); + const RESERVED_JS_KEYWORDS = require("devtools/shared/webconsole/reserved-js-words"); + addEventListener("load", startTest); @@ -106,6 +108,7 @@ doAutocompleteAfterOperator, dontAutocompleteAfterDeclaration, doKeywordsAutocomplete, + dontAutocomplete, ]; if (!isWorker) { @@ -166,7 +169,7 @@ info("test autocomplete for 'dump(window.foobarObject.)'"); let response = await client.autocomplete("dump(window.foobarObject.)"); ok(!response.matchProp, "matchProp"); - is(response.matches.length, 0, "matches.length"); + is(response.matches, null, "matches is null"); } async function doAutocompleteLarge1(client) { @@ -419,23 +422,19 @@ info(`test autocomplete for '['`); res = await client.autocomplete(`[`); - is(res.matches.length, 0, "it does not return anything"); - is(res.isElementAccess, false); + is(res.matches, null, "it does not return anything"); info(`test autocomplete for '[1,2,3'`); res = await client.autocomplete(`[1,2,3`); - is(res.matches.length, 0, "it does not return anything"); - is(res.isElementAccess, false); + is(res.matches, null, "it does not return anything"); info(`test autocomplete for '["'`); res = await client.autocomplete(`["`); - is(res.matches.length, 0, "it does not return anything"); - is(res.isElementAccess, false); + is(res.matches, null, "it does not return anything"); info(`test autocomplete for '[;'`); res = await client.autocomplete(`[;`); - is(res.matches.length, 0, "it does not return anything"); - is(res.isElementAccess, false); + is(res.matches, null, "it does not return anything"); } async function doAutocompleteCommands(client) { @@ -516,23 +515,23 @@ async function dontAutocompleteAfterDeclaration(client) { info("test autocomplete for 'var win'"); let matches = (await client.autocomplete("var win")).matches; - is(matches.length, 0, "no autocompletion on a var declaration"); + is(matches, null, "no autocompletion on a var declaration"); info("test autocomplete for 'const win'"); matches = (await client.autocomplete("const win")).matches; - is(matches.length, 0, "no autocompletion on a const declaration"); + is(matches, null, "no autocompletion on a const declaration"); info("test autocomplete for 'let win'"); matches = (await client.autocomplete("let win")).matches; - is(matches.length, 0, "no autocompletion on a let declaration"); + is(matches, null, "no autocompletion on a let declaration"); info("test autocomplete for 'function win'"); matches = (await client.autocomplete("function win")).matches; - is(matches.length, 0, "no autocompletion on a function declaration"); + is(matches, null, "no autocompletion on a function declaration"); info("test autocomplete for 'class win'"); matches = (await client.autocomplete("class win")).matches; - is(matches.length, 0, "no autocompletion on a class declaration"); + is(matches, null, "no autocompletion on a class declaration"); info("test autocomplete for 'const win = win'"); matches = (await client.autocomplete("const win = win")).matches; @@ -565,6 +564,50 @@ async function doKeywordsAutocomplete(client) { "'function' is not returned when doing an element access"); } + async function dontAutocomplete(client) { + const inputs = [ + "", + " ", + "\n", + "\n ", + " \n ", + " \n", + "true;", + "true,", + "({key:", + "a=", + "if(a<", + "if(a>", + "1+", + "1-", + "++", + "--", + "1*", + "2**", + "1/", + "1%", + "1|", + "1&", + "1^", + "~", + "1<<", + "1>>", + "1>>>", + "false||", + "false&&", + "x=true?", + "x=false?1:", + "!", + ...RESERVED_JS_KEYWORDS.map(keyword => `${keyword} `), + ...RESERVED_JS_KEYWORDS.map(keyword => `${keyword} `), + ]; + for (const input of inputs) { + info(`test autocomplete for "${input}"`); + let matches = (await client.autocomplete(input)).matches; + is(matches, null, `No autocomplete result for ${input}"`); + } + } + async function getAutocompleteMatches(client, input) { info(`test autocomplete for "${input}"`); const res = (await client.autocomplete(input));