tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_webconsole_context_menu_copy_object.js (8480B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 // Test the "Copy object" menu item of the webconsole is enabled only when
      5 // clicking on messages that are associated with an object actor.
      6 
      7 "use strict";
      8 
      9 const TEST_URI = `data:text/html;charset=utf-8,<!DOCTYPE html><script>
     10  window.bar = { baz: 1 };
     11  console.log("foo");
     12  console.log("foo", window.bar);
     13  console.log(["foo", window.bar, 2]);
     14  console.group("group");
     15  console.groupCollapsed("collapsed");
     16  console.groupEnd();
     17  console.log(532);
     18  console.log(true);
     19  console.log(false);
     20  console.log(undefined);
     21  console.log(null);
     22  /* Verify that the conflicting binding on user code doesn't break the
     23   * functionality. */
     24  function copy() { alert("user-defined function is called"); }
     25  /* Check that trying to copy an object that can't be serialized displays an error in the UI */
     26  var cyclical = {}; cyclical.cycle = cyclical;
     27  console.log(cyclical);
     28  
     29  /* Verify that custom formatters don't break copying. */
     30  window.devtoolsFormatters = [
     31    {
     32      header: (obj, config) => {
     33        if (!obj?.useCustomFormatter)
     34          return null;
     35        return [
     36          "span",
     37          { "style": "color: red" },
     38          "Hello, ",
     39          [
     40            "span",
     41            { "style": "color: green" },
     42            "world!"
     43          ]
     44        ];
     45      },
     46      hasBody: (obj) => {
     47        return false;
     48      },
     49    }
     50  ];
     51  console.log({ useCustomFormatter: true, a: 5 });
     52 </script>`;
     53 const copyObjectMenuItemId = "#console-menu-copy-object";
     54 
     55 add_task(async function () {
     56  await pushPref("devtools.custom-formatters.enabled", true);
     57 
     58  const hud = await openNewTabAndConsole(TEST_URI);
     59 
     60  // Reload the browser to ensure the custom formatters are picked up
     61  await reloadBrowser();
     62 
     63  const [msgWithText, msgWithObj, msgNested] = await waitFor(() =>
     64    findConsoleAPIMessages(hud, "foo")
     65  );
     66  ok(
     67    msgWithText && msgWithObj && msgNested,
     68    "Three messages should have appeared"
     69  );
     70 
     71  const [groupMsgObj] = await waitFor(() =>
     72    findMessagePartsByType(hud, {
     73      text: "group",
     74      typeSelector: ".console-api",
     75      partSelector: ".message-body",
     76    })
     77  );
     78  const [collapsedGroupMsgObj] = await waitFor(() =>
     79    findMessagePartsByType(hud, {
     80      text: "collapsed",
     81      typeSelector: ".console-api",
     82      partSelector: ".message-body",
     83    })
     84  );
     85  const [numberMsgObj] = await waitFor(() =>
     86    findMessagePartsByType(hud, {
     87      text: `532`,
     88      typeSelector: ".console-api",
     89      partSelector: ".message-body",
     90    })
     91  );
     92  const [trueMsgObj] = await waitFor(() =>
     93    findMessagePartsByType(hud, {
     94      text: `true`,
     95      typeSelector: ".console-api",
     96      partSelector: ".message-body",
     97    })
     98  );
     99  const [falseMsgObj] = await waitFor(() =>
    100    findMessagePartsByType(hud, {
    101      text: `false`,
    102      typeSelector: ".console-api",
    103      partSelector: ".message-body",
    104    })
    105  );
    106  const [undefinedMsgObj] = await waitFor(() =>
    107    findMessagePartsByType(hud, {
    108      text: `undefined`,
    109      typeSelector: ".console-api",
    110      partSelector: ".message-body",
    111    })
    112  );
    113  const [nullMsgObj] = await waitFor(() =>
    114    findMessagePartsByType(hud, {
    115      text: `null`,
    116      typeSelector: ".console-api",
    117      partSelector: ".message-body",
    118    })
    119  );
    120  const [customMsgObj] = await waitFor(() =>
    121    findMessagePartsByType(hud, {
    122      text: `Hello, world!`,
    123      typeSelector: ".console-api",
    124      partSelector: ".message-body",
    125    })
    126  );
    127  ok(nullMsgObj, "One message with null value should have appeared");
    128 
    129  const text = msgWithText.querySelector(".objectBox-string");
    130  const objInMsgWithObj = msgWithObj.querySelector(".objectBox-object");
    131  const textInMsgWithObj = msgWithObj.querySelector(".objectBox-string");
    132 
    133  // The third message has an object nested in an array, the array is therefore the top
    134  // object, the object is the nested object.
    135  const topObjInMsg = msgNested.querySelector(".objectBox-array");
    136  const nestedObjInMsg = msgNested.querySelector(".objectBox-object");
    137 
    138  const consoleMessages = await waitFor(() =>
    139    findMessagePartsByType(hud, {
    140      text: 'console.log("foo");',
    141      typeSelector: ".console-api",
    142      partSelector: ".message-location",
    143    })
    144  );
    145  await testCopyObjectMenuItemDisabled(hud, consoleMessages[0]);
    146 
    147  info(`Check "Copy object" is enabled for text only messages
    148    thus copying the text`);
    149  await testCopyObject(hud, text, `foo`, false);
    150 
    151  info(`Check "Copy object" is enabled for text in complex messages
    152   thus copying the text`);
    153  await testCopyObject(hud, textInMsgWithObj, `foo`, false);
    154 
    155  info("Check `Copy object` is enabled for objects in complex messages");
    156  await testCopyObject(hud, objInMsgWithObj, `{"baz":1}`, true);
    157 
    158  info("Check `Copy object` is enabled for top object in nested messages");
    159  await testCopyObject(hud, topObjInMsg, `["foo",{"baz":1},2]`, true);
    160 
    161  info("Check `Copy object` is enabled for nested object in nested messages");
    162  await testCopyObject(hud, nestedObjInMsg, `{"baz":1}`, true);
    163 
    164  info("Check `Copy object` is disabled on `console.group('group')` messages");
    165  await testCopyObjectMenuItemDisabled(hud, groupMsgObj);
    166 
    167  info(`Check "Copy object" is disabled in "console.groupCollapsed('collapsed')"
    168    messages`);
    169  await testCopyObjectMenuItemDisabled(hud, collapsedGroupMsgObj);
    170 
    171  // Check for primitive objects
    172  info("Check `Copy object` is enabled for numbers");
    173  await testCopyObject(hud, numberMsgObj, `532`, false);
    174 
    175  info("Check `Copy object` is enabled for booleans");
    176  await testCopyObject(hud, trueMsgObj, `true`, false);
    177  await testCopyObject(hud, falseMsgObj, `false`, false);
    178 
    179  info("Check `Copy object` is enabled for undefined and null");
    180  await testCopyObject(hud, undefinedMsgObj, `undefined`, false);
    181  await testCopyObject(hud, nullMsgObj, `null`, false);
    182 
    183  info("Check `Copy object` is enabled for custom-formatted objects");
    184  await testCopyObject(
    185    hud,
    186    customMsgObj,
    187    `{"useCustomFormatter":true,"a":5}`,
    188    true
    189  );
    190 
    191  info(
    192    "Check `Copy object` for an object with cyclical reference displays an error in the UI"
    193  );
    194  const clipboardContent = SpecialPowers.getClipboardData("text/plain");
    195  const [cyclicalMsgObj] = await waitFor(() =>
    196    findMessagePartsByType(hud, {
    197      text: `cycle`,
    198      typeSelector: ".console-api",
    199      partSelector: ".message-body",
    200    })
    201  );
    202  const menuPopup = await openContextMenu(
    203    hud,
    204    cyclicalMsgObj.querySelector(".objectBox-object")
    205  );
    206  menuPopup.activateItem(menuPopup.querySelector(copyObjectMenuItemId));
    207 
    208  info("Wait until the notification box displays the error");
    209  const notificationBox = await waitFor(() =>
    210    hud.ui.document.getElementById("webconsole-notificationbox")
    211  );
    212  is(
    213    notificationBox.querySelector(".notification").textContent,
    214    "`copy` command failed, object can’t be stringified: TypeError: cyclic object value",
    215    "Notification is displayed with expected message"
    216  );
    217 
    218  // Wait for a bit to check the clipboard isn't overridden
    219  await wait(500);
    220  is(
    221    SpecialPowers.getClipboardData("text/plain"),
    222    clipboardContent,
    223    "clipboard wasn't overridden"
    224  );
    225 });
    226 
    227 async function testCopyObject(hud, element, expectedMessage, objectInput) {
    228  info("Check `Copy object` is enabled");
    229  const menuPopup = await openContextMenu(hud, element);
    230  const copyObjectMenuItem = menuPopup.querySelector(copyObjectMenuItemId);
    231  ok(
    232    !copyObjectMenuItem.disabled,
    233    "`Copy object` is enabled for object in complex message"
    234  );
    235  is(
    236    copyObjectMenuItem.getAttribute("accesskey"),
    237    "o",
    238    "`Copy object` has the right accesskey"
    239  );
    240 
    241  const validatorFn = data => {
    242    const prettifiedMessage = prettyPrintMessage(expectedMessage, objectInput);
    243    return data === prettifiedMessage;
    244  };
    245 
    246  info("Activate item `Copy object`");
    247  await waitForClipboardPromise(
    248    () => menuPopup.activateItem(copyObjectMenuItem),
    249    validatorFn
    250  );
    251 }
    252 
    253 async function testCopyObjectMenuItemDisabled(hud, element) {
    254  const menuPopup = await openContextMenu(hud, element);
    255  const copyObjectMenuItem = menuPopup.querySelector(copyObjectMenuItemId);
    256  ok(
    257    copyObjectMenuItem.disabled,
    258    `"Copy object" is disabled for messages
    259    with no variables/objects`
    260  );
    261  await hideContextMenu(hud);
    262 }
    263 
    264 function prettyPrintMessage(message, isObject) {
    265  return isObject ? JSON.stringify(JSON.parse(message), null, 2) : message;
    266 }