Skip to content

Commit

Permalink
Bug 1216632 - Make autocompletion work on $_ and $0; r=bgrins.
Browse files Browse the repository at this point in the history
To make $0 autocompletion work, we need to pass the current
selectedNode actor from the frontend, so we can retrieve the
object reference later.
For $_, we need the webconsole actor reference to be able
to retrieve the last input result.
Since the list of parameters of JsPropertyProviders was
getting a bit long, we transform them in an object so it's
more legible on the consumer side.
Mochitests are added for both helpers to ensure this work
as expected.

Differential Revision: https://phabricator.services.mozilla.com/D11866

--HG--
extra : moz-landing-system : lando
  • Loading branch information
nchevobbe committed Nov 15, 2018
1 parent 99e9b45 commit 7825246
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 57 deletions.
13 changes: 11 additions & 2 deletions devtools/client/webconsole/actions/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ const {
* - {String} frameActorId: The id of the frame we want to autocomplete in.
* - {Boolean} force: True to force a call to the server (as opposed to retrieve
* from the cache).
* - {String} selectedNodeActor: Actor id of the selected node in the inspector.
*/
function autocompleteUpdate({
inputValue,
cursor,
client,
frameActorId,
force,
selectedNodeActor,
}) {
return ({dispatch, getState}) => {
const {cache} = getState().autocomplete;
Expand All @@ -51,7 +53,12 @@ function autocompleteUpdate({
return dispatch(autoCompleteDataRetrieveFromCache(input));
}

return dispatch(autocompleteDataFetch({input, frameActorId, client}));
return dispatch(autocompleteDataFetch({
input,
frameActorId,
client,
selectedNodeActor,
}));
};
}

Expand Down Expand Up @@ -89,16 +96,18 @@ function generateRequestId() {
* - {String} input: the expression that we want to complete.
* - {String} frameActorId: The id of the frame we want to autocomplete in.
* - {WebConsoleClient} client: The webconsole client.
* - {String} selectedNodeActor: Actor id of the selected node in the inspector.
*/
function autocompleteDataFetch({
input,
frameActorId,
client,
selectedNodeActor,
}) {
return ({dispatch}) => {
const id = generateRequestId();
dispatch({type: AUTOCOMPLETE_PENDING_REQUEST, id});
client.autocomplete(input, undefined, frameActorId).then(res => {
client.autocomplete(input, undefined, frameActorId, selectedNodeActor).then(res => {
dispatch(autocompleteDataReceive(id, input, frameActorId, res));
}).catch(e => {
console.error("failed autocomplete", e);
Expand Down
17 changes: 9 additions & 8 deletions devtools/client/webconsole/components/JSTerm.js
Original file line number Diff line number Diff line change
Expand Up @@ -1129,12 +1129,19 @@ class JSTerm extends Component {
return;
}

let selectedNodeActor = null;
const inspectorSelection = this.hud.owner.getInspectorSelection();
if (inspectorSelection && inspectorSelection.nodeFront) {
selectedNodeActor = inspectorSelection.nodeFront.actorID;
}

this.props.autocompleteUpdate({
inputValue,
cursor,
frameActorId,
force,
client: this.webConsoleClient,
selectedNodeActor,
});
}

Expand Down Expand Up @@ -1612,14 +1619,8 @@ function mapDispatchToProps(dispatch) {
clearHistory: () => dispatch(historyActions.clearHistory()),
updateHistoryPosition: (direction, expression) =>
dispatch(historyActions.updateHistoryPosition(direction, expression)),
autocompleteUpdate: ({inputValue, cursor, frameActorId, force, client}) => dispatch(
autocompleteActions.autocompleteUpdate({
inputValue,
cursor,
frameActorId,
force,
client,
})
autocompleteUpdate: options => dispatch(
autocompleteActions.autocompleteUpdate(options)
),
autocompleteBailOut: () => dispatch(autocompleteActions.autocompleteBailOut()),
};
Expand Down
2 changes: 2 additions & 0 deletions devtools/client/webconsole/test/mochitest/browser.ini
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ skip-if = verify
[browser_jsterm_completion_bracket_cached_results.js]
[browser_jsterm_completion_bracket.js]
[browser_jsterm_completion_case_sensitivity.js]
[browser_jsterm_completion_dollar_underscore.js]
[browser_jsterm_completion_dollar_zero.js]
[browser_jsterm_completion.js]
[browser_jsterm_content_defined_helpers.js]
[browser_jsterm_copy_command.js]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* -*- 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/ */

// Tests that code completion works properly on $_.

"use strict";

const TEST_URI = `data:text/html;charset=utf8,<p>test code completion on $_`;

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("Test that there's no issue when trying to do an autocompletion without last " +
"evaluation result");
await setInputValueForAutocompletion(jsterm, "$_.");
is(autocompletePopup.items.length, 0, "autocomplete popup has no items");
is(autocompletePopup.isOpen, false, "autocomplete popup is not open");

info("Populate $_ by executing a command");
await jsterm.execute(`Object.create(null, Object.getOwnPropertyDescriptors({
x: 1,
y: "hello"
}))`);

await setInputValueForAutocompletion(jsterm, "$_.");
checkJsTermCompletionValue(jsterm, " x", "'$_.' completion (completeNode)");
is(getAutocompletePopupLabels(autocompletePopup).join("|"), "x|y",
"autocomplete popup has expected items");
is(autocompletePopup.isOpen, true, "autocomplete popup is open");

await setInputValueForAutocompletion(jsterm, "$_.x.");
is(autocompletePopup.isOpen, true, "autocomplete popup is open");
is(getAutocompletePopupLabels(autocompletePopup).includes("toExponential"), true,
"autocomplete popup has expected items");

await setInputValueForAutocompletion(jsterm, "$_.y.");
is(autocompletePopup.isOpen, true, "autocomplete popup is open");
is(getAutocompletePopupLabels(autocompletePopup).includes("trim"), true,
"autocomplete popup has expected items");
}

function getAutocompletePopupLabels(autocompletePopup) {
return autocompletePopup.items.map(i => i.label);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* -*- 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/ */

// Tests that code completion works properly on $0.

"use strict";

const TEST_URI = `data:text/html;charset=utf-8,
<head>
<title>$0 completion test</title>
</head>
<body>
<div>
<h1>$0 completion test</h1>
<p>This is some example text</p>
</div>
</body>`;

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 toolbox = await openNewTabAndToolbox(TEST_URI, "inspector");
await registerTestActor(toolbox.target.client);
const testActor = await getTestActor(toolbox);
await selectNodeWithPicker(toolbox, testActor, "h1");

info("Picker mode stopped, <h1> selected, now switching to the console");
const hud = await openConsole();
const {jsterm} = hud;

hud.ui.clearOutput();

const {autocompletePopup} = jsterm;

await setInputValueForAutocompletion(jsterm, "$0.");
is(getAutocompletePopupLabels(autocompletePopup).includes("attributes"), true,
"autocomplete popup has expected items");
is(autocompletePopup.isOpen, true, "autocomplete popup is open");

await setInputValueForAutocompletion(jsterm, "$0.attributes.");
is(autocompletePopup.isOpen, true, "autocomplete popup is open");
is(getAutocompletePopupLabels(autocompletePopup).includes("getNamedItem"), true,
"autocomplete popup has expected items");
}

function getAutocompletePopupLabels(autocompletePopup) {
return autocompletePopup.items.map(i => i.label);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,9 @@ const TEST_URI = `data:text/html;charset=utf-8,

add_task(async function() {
const toolbox = await openNewTabAndToolbox(TEST_URI, "inspector");
const inspector = toolbox.getPanel("inspector");

await registerTestActor(toolbox.target.client);
const testActor = await getTestActor(toolbox);

const onPickerStarted = inspector.toolbox.once("picker-started");
inspector.toolbox.highlighterUtils.startPicker();
await onPickerStarted;

info("Picker mode started, now clicking on <h1> to select that node");
const onPickerStopped = toolbox.once("picker-stopped");
const onInspectorUpdated = inspector.once("inspector-updated");

testActor.synthesizeMouse({
selector: "h1",
center: true,
options: {},
});

await onPickerStopped;
await onInspectorUpdated;
await selectNodeWithPicker(toolbox, testActor, "h1");

info("Picker mode stopped, <h1> selected, now switching to the console");
const hud = await openConsole();
Expand Down
29 changes: 29 additions & 0 deletions devtools/client/webconsole/test/mochitest/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -952,3 +952,32 @@ function isReverseSearchInputFocused(hud) {

return document.activeElement == reverseSearchInput && documentIsFocused;
}

/**
* Selects a node in the inspector.
*
* @param {Object} toolbox
* @param {Object} testActor: A test actor registered on the target. Needed to click on
* the content element.
* @param {String} selector: The selector for the node we want to select.
*/
async function selectNodeWithPicker(toolbox, testActor, selector) {
const inspector = toolbox.getPanel("inspector");

const onPickerStarted = inspector.toolbox.once("picker-started");
inspector.toolbox.highlighterUtils.startPicker();
await onPickerStarted;

info(`Picker mode started, now clicking on "${selector}" to select that node`);
const onPickerStopped = toolbox.once("picker-stopped");
const onInspectorUpdated = inspector.once("inspector-updated");

testActor.synthesizeMouse({
selector,
center: true,
options: {},
});

await onPickerStopped;
await onInspectorUpdated;
}
11 changes: 9 additions & 2 deletions devtools/server/actors/webconsole.js
Original file line number Diff line number Diff line change
Expand Up @@ -1227,8 +1227,15 @@ WebConsoleActor.prototype =
dbgObject = this.dbg.addDebuggee(this.evalWindow);
}

const result = JSPropertyProvider(dbgObject, environment, request.text,
request.cursor, frameActorId) || {};
const result = JSPropertyProvider({
dbgObject,
environment,
inputValue: request.text,
cursor: request.cursor,
invokeUnsafeGetter: false,
webconsoleActor: this,
selectedNodeActor: request.selectedNodeActor,
}) || {};

if (!hadDebuggee && dbgObject) {
this.dbg.removeDebuggee(this.evalWindow);
Expand Down
14 changes: 8 additions & 6 deletions devtools/shared/webconsole/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,22 +370,24 @@ WebConsoleClient.prototype = {
/**
* Autocomplete a JavaScript expression.
*
* @param string string
* @param {String} string
* The code you want to autocomplete.
* @param number cursor
* @param {Number} cursor
* Cursor location inside the string. Index starts from 0.
* @param string frameActor
* @param {String} frameActor
* The id of the frame actor that made the call.
* @param {String} selectedNodeActor: Actor id of the selected node in the inspector.
* @return request
* Request object that implements both Promise and EventEmitter interfaces
*/
autocomplete: function(string, cursor, frameActor) {
autocomplete: function(string, cursor, frameActor, selectedNodeActor) {
const packet = {
to: this._actor,
type: "autocomplete",
text: string,
cursor: cursor,
frameActor: frameActor,
cursor,
frameActor,
selectedNodeActor,
};
return this._client.request(packet);
},
Expand Down
Loading

0 comments on commit 7825246

Please sign in to comment.