summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/test/browser/browser_jsterm_eager_evaluation.js
blob: dae99c196175fcb7a1459ed4554a53c0e3b581fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/* 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 TEST_URI = `data:text/html;charset=utf-8,<!DOCTYPE html>
<script>
let x = 3, y = 4;
function zzyzx() {
  x = 10;
}
function zzyzx2() {
  x = 10;
}
var obj = {propA: "A", propB: "B"};
var array = [1, 2, 3];
var $$ = 42;
</script>
<h1>title</h1>
`;

const EAGER_EVALUATION_PREF = "devtools.webconsole.input.eagerEvaluation";

// Basic testing of eager evaluation functionality. Expressions which can be
// eagerly evaluated should show their results, and expressions with side
// effects should not perform those side effects.
add_task(async function () {
  // Open the inspector first to select a node, so that we can later test "$0"
  const toolbox = await openNewTabAndToolbox(TEST_URI, "inspector");
  await selectNodeWithPicker(toolbox, "h1");

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

  // Do an evaluation to populate $_
  await executeAndWaitForResultMessage(
    hud,
    "'result: ' + (x + y)",
    "result: 7"
  );

  setInputValue(hud, "x + y");
  await waitForEagerEvaluationResult(hud, "7");

  setInputValue(hud, "x + y + undefined");
  await waitForEagerEvaluationResult(hud, "NaN");

  setInputValue(hud, "1 - 1");
  await waitForEagerEvaluationResult(hud, "0");

  setInputValue(hud, "!true");
  await waitForEagerEvaluationResult(hud, "false");

  setInputValue(hud, `"ab".slice(0, 0)`);
  await waitForEagerEvaluationResult(hud, `""`);

  setInputValue(hud, `JSON.parse("null")`);
  await waitForEagerEvaluationResult(hud, "null");

  setInputValue(hud, "-x / 0");
  await waitForEagerEvaluationResult(hud, "-Infinity");

  setInputValue(hud, "x = 10");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "x + 1");
  await waitForEagerEvaluationResult(hud, "4");

  setInputValue(hud, "zzyzx()");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "x + 2");
  await waitForEagerEvaluationResult(hud, "5");

  setInputValue(hud, "x +");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "x + z");
  await waitForEagerEvaluationResult(hud, /ReferenceError/);

  setInputValue(hud, "var a = 5");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "x + a");
  await waitForEagerEvaluationResult(hud, /ReferenceError/);

  setInputValue(hud, '"foobar".slice(1, 5)');
  await waitForEagerEvaluationResult(hud, '"ooba"');

  setInputValue(hud, '"foobar".toString()');
  await waitForEagerEvaluationResult(hud, '"foobar"');

  setInputValue(hud, "(new Array()).push(3)");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "(new Uint32Array([1,2,3])).includes(2)");
  await waitForEagerEvaluationResult(hud, "true");

  setInputValue(hud, "Math.round(3.2)");
  await waitForEagerEvaluationResult(hud, "3");

  info("Check web console commands");
  setInputValue(hud, "help()");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "$0");
  await waitForEagerEvaluationResult(hud, `<h1>`);

  setInputValue(hud, "$('html')");
  await waitForEagerEvaluationResult(hud, `<html>`);

  setInputValue(hud, "$$");
  await waitForEagerEvaluationResult(hud, `42`);

  info("Check that $_ wasn't polluted by eager evaluations");
  setInputValue(hud, "$_");
  await waitForEagerEvaluationResult(hud, `"result: 7"`);

  setInputValue(hud, "'> ' + $_");
  await waitForEagerEvaluationResult(hud, `"> result: 7"`);

  info("Switch to editor mode");
  await toggleLayout(hud);
  await waitForEagerEvaluationResult(hud, `"> result: 7"`);
  ok(true, "eager evaluation is still displayed in editor mode");

  setInputValue(hud, "4 + 7");
  await waitForEagerEvaluationResult(hud, "11");

  // go back to inline layout.
  await toggleLayout(hud);

  setInputValue(hud, "typeof new Proxy({}, {})");
  await waitForEagerEvaluationResult(hud, `"object"`);

  setInputValue(hud, "typeof Proxy.revocable({}, {}).revoke");
  await waitForEagerEvaluationResult(hud, `"function"`);

  setInputValue(hud, "Reflect.apply(() => 1, null, [])");
  await waitForEagerEvaluationResult(hud, "1");
  setInputValue(
    hud,
    `Reflect.apply(() => {
      globalThis.sideEffect = true;
      return 2;
    }, null, [])`
  );
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.construct(Array, []).length");
  await waitForEagerEvaluationResult(hud, "0");
  setInputValue(
    hud,
    `Reflect.construct(function() {
      globalThis.sideEffect = true;
    }, [])`
  );
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.defineProperty({}, 'a', {value: 1})");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.deleteProperty({a: 1}, 'a')");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.get({a: 1}, 'a')");
  await waitForEagerEvaluationResult(hud, "1");
  setInputValue(hud, "Reflect.get({get a(){return 2}, 'a')");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.getOwnPropertyDescriptor({a: 1}, 'a').value");
  await waitForEagerEvaluationResult(hud, "1");
  setInputValue(
    hud,
    `Reflect.getOwnPropertyDescriptor(
      new Proxy({ a: 2 }, { getOwnPropertyDescriptor() {
        globalThis.sideEffect = true;
        return { value: 2 };
      }}),
      "a"
    )`
  );
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.getPrototypeOf({}) === Object.prototype");
  await waitForEagerEvaluationResult(hud, "true");
  setInputValue(
    hud,
    `Reflect.getPrototypeOf(
      new Proxy({}, { getPrototypeOf() {
        globalThis.sideEffect = true;
        return null;
      }})
    )`
  );
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.has({a: 1}, 'a')");
  await waitForEagerEvaluationResult(hud, "true");
  setInputValue(
    hud,
    `Reflect.has(
      new Proxy({ a: 2 }, { has() {
        globalThis.sideEffect = true;
        return true;
      }}), "a"
    )`
  );
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.isExtensible({})");
  await waitForEagerEvaluationResult(hud, "true");
  setInputValue(
    hud,
    `Reflect.isExtensible(
      new Proxy({}, { isExtensible() {
        globalThis.sideEffect = true;
        return true;
      }})
    )`
  );
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.ownKeys({a: 1})[0]");
  await waitForEagerEvaluationResult(hud, `"a"`);
  setInputValue(
    hud,
    `Reflect.ownKeys(
      new Proxy({}, { ownKeys() {
        globalThis.sideEffect = true;
        return ['a'];
      }})
    )`
  );
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.preventExtensions({})");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.set({}, 'a', 1)");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "Reflect.setPrototypeOf({}, null)");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "[] instanceof Array");
  await waitForEagerEvaluationResult(hud, "true");

  setInputValue(hud, "Int8Array.from({length: 1})[0]");
  await waitForEagerEvaluationResult(hud, "0");

  setInputValue(hud, "Float64Array.of(1)[0]");
  await waitForEagerEvaluationResult(hud, "1");

  setInputValue(hud, "array.fill()");
  await waitForNoEagerEvaluationResult(hud);

  setInputValue(hud, "array");
  await waitForEagerEvaluationResult(hud, "Array(3) [ 1, 2, 3 ]");

  info("Check that top-level await expression are not evaluated");
  setInputValue(hud, "await 1; 2 + 3;");
  await waitForNoEagerEvaluationResult(hud);
  ok(true, "instant evaluation is disabled for top-level await expressions");
});

// Test that the currently selected autocomplete result is eagerly evaluated.
add_task(async function () {
  const hud = await openNewTabAndConsole(TEST_URI);
  const { jsterm } = hud;

  const { autocompletePopup: popup } = jsterm;

  ok(!popup.isOpen, "popup is not open");
  let onPopupOpen = popup.once("popup-opened");
  EventUtils.sendString("zzy");
  await onPopupOpen;

  await waitForEagerEvaluationResult(hud, "function zzyzx()");
  EventUtils.synthesizeKey("KEY_ArrowDown");
  await waitForEagerEvaluationResult(hud, "function zzyzx2()");

  // works when the input isn't properly cased but matches an autocomplete item
  setInputValue(hud, "o");
  onPopupOpen = popup.once("popup-opened");
  EventUtils.sendString("B");
  await waitForEagerEvaluationResult(hud, `Object { propA: "A", propB: "B" }`);

  // works when doing element access without quotes
  setInputValue(hud, "obj[p");
  onPopupOpen = popup.once("popup-opened");
  EventUtils.sendString("RoP");
  await waitForEagerEvaluationResult(hud, `"A"`);

  EventUtils.synthesizeKey("KEY_ArrowDown");
  await waitForEagerEvaluationResult(hud, `"B"`);

  // closing the autocomplete popup updates the eager evaluation result
  let onPopupClose = popup.once("popup-closed");
  EventUtils.synthesizeKey("KEY_Escape");
  await onPopupClose;
  await waitForNoEagerEvaluationResult(hud);

  info(
    "Check that closing the popup by adding a space will update the instant eval result"
  );
  await setInputValueForAutocompletion(hud, "x");
  await waitForEagerEvaluationResult(hud, "3");

  EventUtils.synthesizeKey("KEY_ArrowDown");
  // Navigates to the XMLDocument item in the popup
  await waitForEagerEvaluationResult(hud, `function XMLDocument()`);

  onPopupClose = popup.once("popup-closed");
  EventUtils.sendString(" ");
  await waitForEagerEvaluationResult(hud, `3`);
});

// Test that the setting works as expected.
add_task(async function () {
  // start with the pref off.
  await pushPref(EAGER_EVALUATION_PREF, false);
  const hud = await openNewTabAndConsole(TEST_URI);

  info("Check that the setting is disabled");
  checkConsoleSettingState(
    hud,
    ".webconsole-console-settings-menu-item-eager-evaluation",
    false
  );

  // Wait for the autocomplete popup to be displayed so we know the eager evaluation could
  // have occured.
  const onPopupOpen = hud.jsterm.autocompletePopup.once("popup-opened");
  await setInputValueForAutocompletion(hud, "x + y");
  await onPopupOpen;

  is(
    getEagerEvaluationElement(hud),
    null,
    "There's no eager evaluation element"
  );
  hud.jsterm.autocompletePopup.hidePopup();

  info("Turn on the eager evaluation");
  toggleConsoleSetting(
    hud,
    ".webconsole-console-settings-menu-item-eager-evaluation"
  );
  await waitFor(() => getEagerEvaluationElement(hud));
  ok(true, "The eager evaluation element is now displayed");
  is(
    Services.prefs.getBoolPref(EAGER_EVALUATION_PREF),
    true,
    "Pref was changed"
  );

  setInputValue(hud, "1 + 2");
  await waitForEagerEvaluationResult(hud, "3");
  ok(true, "Eager evaluation result is displayed");

  info("Turn off the eager evaluation");
  toggleConsoleSetting(
    hud,
    ".webconsole-console-settings-menu-item-eager-evaluation"
  );
  await waitFor(() => !getEagerEvaluationElement(hud));
  is(
    Services.prefs.getBoolPref(EAGER_EVALUATION_PREF),
    false,
    "Pref was changed"
  );
  ok(true, "Eager evaluation element is no longer displayed");

  // reset the preference
  await pushPref(EAGER_EVALUATION_PREF, true);
});

// Test that the console instant evaluation is updated on page navigation
add_task(async function () {
  const start_uri = "data:text/html, Start uri";
  const new_uri = "data:text/html, Test console refresh instant value";
  const hud = await openNewTabAndConsole(start_uri);

  setInputValue(hud, "globalThis.location.href");
  await waitForEagerEvaluationResult(hud, `"${start_uri}"`);

  await navigateTo(new_uri);
  await waitForEagerEvaluationResult(hud, `"${new_uri}"`);
});