Bug 1216632 - Make autocompletion work on $_ and $0; r=bgrins.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Thu, 15 Nov 2018 09:49:14 +0000
changeset 446560 18eddae8a2682764bbc72f19b7644e892ffcfe6a
parent 446559 acb7047f9492ce3d9fc299f0fbecf1f9c53a5ff9
child 446561 8a8fbc85088e09377c0a570cf7c9ec537e099b8f
push id35043
push userebalazs@mozilla.com
push dateThu, 15 Nov 2018 16:12:36 +0000
treeherdermozilla-central@59026ada59bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1216632
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1216632 - Make autocompletion work on $_ and $0; r=bgrins. 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
devtools/client/webconsole/actions/autocomplete.js
devtools/client/webconsole/components/JSTerm.js
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_underscore.js
devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_zero.js
devtools/client/webconsole/test/mochitest/browser_webconsole_highlighter_console_helper.js
devtools/client/webconsole/test/mochitest/head.js
devtools/server/actors/webconsole.js
devtools/shared/webconsole/client.js
devtools/shared/webconsole/js-property-provider.js
devtools/shared/webconsole/test/unit/test_js_property_provider.js
--- a/devtools/client/webconsole/actions/autocomplete.js
+++ b/devtools/client/webconsole/actions/autocomplete.js
@@ -16,23 +16,25 @@ const {
  *
  * @param {Object} Object of the following shape:
  *        - {String} inputValue: the expression to complete.
  *        - {Int} cursor: The position of the cursor in the inputValue.
  *        - {WebConsoleClient} client: The webconsole client.
  *        - {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;
 
     if (!force && (
       !inputValue ||
       /^[a-zA-Z0-9_$]/.test(inputValue.substring(cursor))
     )) {
@@ -46,17 +48,22 @@ function autocompleteUpdate({
       input.startsWith(cache.input) &&
       /[a-zA-Z0-9]$/.test(input) &&
       frameActorId === cache.frameActorId;
 
     if (retrieveFromCache) {
       return dispatch(autoCompleteDataRetrieveFromCache(input));
     }
 
-    return dispatch(autocompleteDataFetch({input, frameActorId, client}));
+    return dispatch(autocompleteDataFetch({
+      input,
+      frameActorId,
+      client,
+      selectedNodeActor,
+    }));
   };
 }
 
 /**
  * Called when the autocompletion data should be cleared.
  */
 function autocompleteClear() {
   return {
@@ -84,26 +91,28 @@ function generateRequestId() {
 
 /**
  * Action that fetch autocompletion data from the server.
  *
  * @param {Object} Object of the following shape:
  *        - {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);
       dispatch(autocompleteClear());
     });
   };
 }
 
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -1124,22 +1124,29 @@ class JSTerm extends Component {
       (inputNode && inputNode.selectionStart != inputNode.selectionEnd) ||
       (editor && editor.getSelection())
     ) {
       this.clearCompletion();
       this.emit("autocomplete-updated");
       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,
     });
   }
 
   /**
    * Takes the data returned by the server and update the autocomplete popup state (i.e.
    * its visibility and items).
    *
    * @param {Object} data
@@ -1607,22 +1614,16 @@ function mapStateToProps(state) {
 
 function mapDispatchToProps(dispatch) {
   return {
 
     appendToHistory: (expr) => dispatch(historyActions.appendToHistory(expr)),
     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()),
   };
 }
 
 module.exports = connect(mapStateToProps, mapDispatchToProps)(JSTerm);
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -208,16 +208,18 @@ skip-if = verify
 [browser_jsterm_await_concurrent.js]
 [browser_jsterm_await_error.js]
 [browser_jsterm_await_helper_dollar_underscore.js]
 [browser_jsterm_await_paused.js]
 [browser_jsterm_await.js]
 [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]
 [browser_jsterm_ctrl_a_select_all.js]
 [browser_jsterm_ctrl_key_nav.js]
 skip-if = os != 'mac' # The tested ctrl+key shortcuts are OSX only
 [browser_jsterm_document_no_xray.js]
 [browser_jsterm_error_docs.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_underscore.js
@@ -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);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_zero.js
@@ -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);
+}
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_highlighter_console_helper.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_highlighter_console_helper.js
@@ -17,37 +17,19 @@ const TEST_URI = `data:text/html;charset
   </div>
   <div>
     <p>${loremIpsum()}</p>
   </div>
 </body>`.replace("\n", "");
 
 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();
   const {jsterm} = hud;
 
   hud.ui.clearOutput();
 
   const onEvaluationResult = waitForMessage(hud, "<h1>");
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -947,8 +947,37 @@ function getReverseSearchInfoElement(hud
 function isReverseSearchInputFocused(hud) {
   const {outputNode} = hud.ui;
   const document = outputNode.ownerDocument;
   const documentIsFocused = document.hasFocus();
   const reverseSearchInput = outputNode.querySelector(".reverse-search-input");
 
   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;
+}
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1222,18 +1222,25 @@ WebConsoleActor.prototype =
             Error("The frame actor was not found: " + frameActorId));
         }
       } else {
         // This is the general case (non-paused debugger)
         hadDebuggee = this.dbg.hasDebuggee(this.evalWindow);
         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);
       }
 
       matches = result.matches || new Set();
       matchProp = result.matchProp;
       isElementAccess = result.isElementAccess;
--- a/devtools/shared/webconsole/client.js
+++ b/devtools/shared/webconsole/client.js
@@ -365,32 +365,34 @@ WebConsoleClient.prototype = {
         "No response handler for an evaluateJSAsync result (resultID: " +
                                     packet.resultID + ")");
     }
   },
 
   /**
    * 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);
   },
 
   /**
    * Clear the cache of messages (page errors and console API calls).
    *
    * @return request
--- a/devtools/shared/webconsole/js-property-provider.js
+++ b/devtools/shared/webconsole/js-property-provider.js
@@ -189,35 +189,41 @@ function analyzeInputString(str) {
 
   return buildReturnObject();
 }
 
 /**
  * Provides a list of properties, that are possible matches based on the passed
  * Debugger.Environment/Debugger.Object and inputValue.
  *
- * @param {Object} dbgObject
+ * @param {Object} An object of the following shape:
+ * - {Object} dbgObject
  *        When the debugger is not paused this Debugger.Object wraps
  *        the scope for autocompletion.
  *        It is null if the debugger is paused.
- * @param {Object} anEnvironment
+ * - {Object} environment
  *        When the debugger is paused this Debugger.Environment is the
  *        scope for autocompletion.
  *        It is null if the debugger is not paused.
- * @param {String} inputValue
+ * - {String} inputValue
  *        Value that should be completed.
- * @param {Number} cursor (defaults to inputValue.length).
+ * - {Number} cursor (defaults to inputValue.length).
  *        Optional offset in the input where the cursor is located. If this is
  *        omitted then the cursor is assumed to be at the end of the input
  *        value.
- * @param {Boolean} invokeUnsafeGetter (defaults to false).
+ * - {Boolean} invokeUnsafeGetter (defaults to false).
  *        Optional boolean to indicate if the function should execute unsafe getter
  *        in order to retrieve its result's properties.
  *        ⚠️ This should be set to true *ONLY* on user action as it may cause side-effects
  *        in the content page ⚠️
+ * - {WebconsoleActor} webconsoleActor
+ *        A reference to a webconsole actor which we can use to retrieve the last
+ *        evaluation result or create a debuggee value.
+ * - {String}: selectedNodeActor
+ *        The actor id of the selected node in the inspector.
  * @returns null or object
  *          If the inputValue is an unsafe getter and invokeUnsafeGetter is false, the
  *          following form is returned:
  *
  *          {
  *            isUnsafeGetter: true,
  *            getterName: {String} The name of the unsafe getter
  *          }
@@ -229,23 +235,25 @@ function analyzeInputString(str) {
  *            {
  *              matches: Set<string>
  *              matchProp: Last part of the inputValue that was used to find
  *                         the matches-strings.
  *              isElementAccess: Boolean set to true if the evaluation is an element
  *                               access (e.g. `window["addEvent`).
  *            }
  */
-function JSPropertyProvider(
+function JSPropertyProvider({
   dbgObject,
-  anEnvironment,
+  environment,
   inputValue,
   cursor,
-  invokeUnsafeGetter = false
-) {
+  invokeUnsafeGetter = false,
+  webconsoleActor,
+  selectedNodeActor,
+}) {
   if (cursor === undefined) {
     cursor = inputValue.length;
   }
 
   inputValue = inputValue.substring(0, cursor);
 
   // Analyse the inputValue and find the beginning of the last part that
   // should be completed.
@@ -360,17 +368,17 @@ function JSPropertyProvider(
     elementAccessQuote = search[0];
     search = search.replace(startQuoteRegex, "");
   }
 
   let obj = dbgObject;
 
   // The first property must be found in the environment of the paused debugger
   // or of the global lexical scope.
-  const env = anEnvironment || obj.asEnvironment();
+  const env = environment || obj.asEnvironment();
 
   if (properties.length === 0) {
     return {
       isElementAccess,
       matchProp,
       matches: getMatchedPropsInEnvironment(env, search),
     };
   }
@@ -379,16 +387,27 @@ function JSPropertyProvider(
   if (firstProp === "this") {
     // Special case for 'this' - try to get the Object from the Environment.
     // No problem if it throws, we will just not autocomplete.
     try {
       obj = env.object;
     } catch (e) {
       // Ignore.
     }
+  } else if (firstProp === "$_" && webconsoleActor) {
+    obj = webconsoleActor.getLastConsoleInputEvaluation();
+  } else if (firstProp === "$0" && selectedNodeActor && webconsoleActor) {
+    const actor = webconsoleActor.conn.getActor(selectedNodeActor);
+    if (actor) {
+      try {
+        obj = webconsoleActor.makeDebuggeeValue(actor.rawNode);
+      } catch (e) {
+        // Ignore.
+      }
+    }
   } else if (hasArrayIndex(firstProp)) {
     obj = getArrayMemberProperty(null, env, firstProp);
   } else {
     obj = getVariableInEnvironment(env, firstProp);
   }
 
   if (!isObjectUsable(obj)) {
     return null;
@@ -522,25 +541,25 @@ function isObjectUsable(object) {
   }
 
   return true;
 }
 
 /**
  * @see getExactMatchImpl()
  */
-function getVariableInEnvironment(anEnvironment, name) {
-  return getExactMatchImpl(anEnvironment, name, DebuggerEnvironmentSupport);
+function getVariableInEnvironment(environment, name) {
+  return getExactMatchImpl(environment, name, DebuggerEnvironmentSupport);
 }
 
 /**
  * @see getMatchedPropsImpl()
  */
-function getMatchedPropsInEnvironment(anEnvironment, match) {
-  return getMatchedPropsImpl(anEnvironment, match, DebuggerEnvironmentSupport);
+function getMatchedPropsInEnvironment(environment, match) {
+  return getMatchedPropsImpl(environment, match, DebuggerEnvironmentSupport);
 }
 
 /**
  * @see getMatchedPropsImpl()
  */
 function getMatchedPropsInDbgObject(dbgObject, match) {
   return getMatchedPropsImpl(dbgObject, match, DebuggerObjectSupport);
 }
--- a/devtools/shared/webconsole/test/unit/test_js_property_provider.js
+++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js
@@ -76,18 +76,23 @@ function run_test() {
 
   info("Running tests with dbgObject");
   runChecks(dbgObject, null, sandbox);
 
   info("Running tests with dbgEnv");
   runChecks(null, dbgEnv, sandbox);
 }
 
-function runChecks(dbgObject, dbgEnv, sandbox) {
-  const propertyProvider = (...args) => JSPropertyProvider(dbgObject, dbgEnv, ...args);
+function runChecks(dbgObject, environment, sandbox) {
+  const propertyProvider = (inputValue, options) => JSPropertyProvider({
+    dbgObject,
+    environment,
+    inputValue,
+    ...options,
+  });
 
   info("Test that suggestions are given for 'this'");
   let results = propertyProvider("t");
   test_has_result(results, "this");
 
   if (dbgObject != null) {
     info("Test that suggestions are given for 'this.'");
     results = propertyProvider("this.");
@@ -236,30 +241,30 @@ function runChecks(dbgObject, dbgEnv, sa
 
   results = propertyProvider("testGetters['y']['y'].");
   Assert.ok(results === null);
 
   results = propertyProvider("testGetters.y['y'].");
   Assert.ok(results === null);
 
   info("Test that getters are executed if invokeUnsafeGetter is true");
-  results = propertyProvider("testGetters.x.", undefined, true);
+  results = propertyProvider("testGetters.x.", {invokeUnsafeGetter: true});
   test_has_exact_results(results, ["hello", "world"]);
   Assert.ok(Object.keys(results).includes("isUnsafeGetter") === false);
   Assert.ok(Object.keys(results).includes("getterName") === false);
 
   info("Test that executing getters filters with provided string");
-  results = propertyProvider("testGetters.x.hell", undefined, true);
+  results = propertyProvider("testGetters.x.hell", {invokeUnsafeGetter: true});
   test_has_exact_results(results, ["hello"]);
 
-  results = propertyProvider("testGetters.x['hell", undefined, true);
+  results = propertyProvider("testGetters.x['hell", {invokeUnsafeGetter: true});
   test_has_exact_results(results, ["'hello'"]);
 
   info("Test that children getters are executed if invokeUnsafeGetter is true");
-  results = propertyProvider("testGetters.y.y.", undefined, true);
+  results = propertyProvider("testGetters.y.y.", {invokeUnsafeGetter: true});
   test_has_result(results, "trim");
 
   info("Test with number literals");
   results = propertyProvider("1.");
   Assert.ok(results === null, "Does not complete on possible floating number");
 
   results = propertyProvider("(1)..");
   Assert.ok(results === null, "Does not complete on invalid syntax");