tor-browser

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

browser_RemoteValueDOM.js (23917B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 /* eslint no-undef: 0 no-unused-vars: 0 */
      7 
      8 add_setup(function () {
      9  return SpecialPowers.pushPrefEnv({
     10    set: [["security.allow_eval_with_system_principal", true]],
     11  });
     12 });
     13 
     14 add_task(async function test_deserializeSharedIdInvalidTypes() {
     15  await runTestInContent(() => {
     16    for (const invalidType of [false, 42, {}, []]) {
     17      info(`Checking type: '${invalidType}'`);
     18 
     19      const serializedValue = {
     20        sharedId: invalidType,
     21      };
     22 
     23      Assert.throws(
     24        () => deserialize(serializedValue, realm, { nodeCache }),
     25        /InvalidArgumentError:/,
     26        `Got expected error for type ${invalidType}`
     27      );
     28    }
     29  });
     30 });
     31 
     32 add_task(async function test_deserializeSharedIdInvalidValue() {
     33  await runTestInContent(() => {
     34    const serializedValue = {
     35      sharedId: "foo",
     36    };
     37 
     38    Assert.throws(
     39      () => deserialize(serializedValue, realm, { nodeCache }),
     40      /NoSuchNodeError:/,
     41      "Got expected error for unknown 'sharedId'"
     42    );
     43  });
     44 });
     45 
     46 add_task(async function test_deserializeSharedId() {
     47  await loadURL(inline("<div>"));
     48 
     49  await runTestInContent(() => {
     50    const domEl = content.document.querySelector("div");
     51    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
     52 
     53    const serializedValue = {
     54      sharedId: domElRef,
     55    };
     56 
     57    const node = deserialize(serializedValue, realm, { nodeCache });
     58 
     59    Assert.equal(node, domEl);
     60  });
     61 });
     62 
     63 add_task(async function test_deserializeSharedIdPrecedenceOverHandle() {
     64  await loadURL(inline("<div>"));
     65 
     66  await runTestInContent(() => {
     67    const domEl = content.document.querySelector("div");
     68    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
     69 
     70    const serializedValue = {
     71      handle: "foo",
     72      sharedId: domElRef,
     73    };
     74 
     75    const node = deserialize(serializedValue, realm, { nodeCache });
     76 
     77    Assert.equal(node, domEl);
     78  });
     79 });
     80 
     81 add_task(async function test_deserializeSharedIdNoWindowRealm() {
     82  await loadURL(inline("<div>"));
     83 
     84  await runTestInContent(() => {
     85    const domEl = content.document.querySelector("div");
     86    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
     87 
     88    const serializedValue = {
     89      sharedId: domElRef,
     90    };
     91 
     92    Assert.throws(
     93      () => deserialize(serializedValue, new Realm(), { nodeCache }),
     94      /NoSuchNodeError/,
     95      `Got expected error for a non-window realm`
     96    );
     97  });
     98 });
     99 
    100 // Bug 1819902: Instead of a browsing context check compare the origin
    101 add_task(async function test_deserializeSharedIdOtherBrowsingContext() {
    102  await loadURL(inline("<iframe>"));
    103 
    104  await runTestInContent(() => {
    105    const iframeEl = content.document.querySelector("iframe");
    106    const domEl = iframeEl.contentWindow.document.createElement("div");
    107    iframeEl.contentWindow.document.body.appendChild(domEl);
    108 
    109    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
    110 
    111    const serializedValue = {
    112      sharedId: domElRef,
    113    };
    114 
    115    const node = deserialize(serializedValue, realm, { nodeCache });
    116 
    117    Assert.equal(node, null);
    118  });
    119 });
    120 
    121 add_task(async function test_serializeRemoteComplexValues() {
    122  await loadURL(inline("<div>"));
    123 
    124  await runTestInContent(() => {
    125    const domEl = content.document.querySelector("div");
    126    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
    127 
    128    const REMOTE_COMPLEX_VALUES = [
    129      {
    130        value: content.document.querySelector("div"),
    131        serialized: {
    132          type: "node",
    133          sharedId: domElRef,
    134          value: {
    135            attributes: {},
    136            childNodeCount: 0,
    137            localName: "div",
    138            namespaceURI: "http://www.w3.org/1999/xhtml",
    139            nodeType: 1,
    140            shadowRoot: null,
    141          },
    142        },
    143      },
    144      {
    145        value: content.document.querySelectorAll("div"),
    146        serialized: {
    147          type: "nodelist",
    148          value: [
    149            {
    150              type: "node",
    151              sharedId: domElRef,
    152              value: {
    153                nodeType: 1,
    154                localName: "div",
    155                namespaceURI: "http://www.w3.org/1999/xhtml",
    156                childNodeCount: 0,
    157                attributes: {},
    158                shadowRoot: null,
    159              },
    160            },
    161          ],
    162        },
    163      },
    164      {
    165        value: content.document.getElementsByTagName("div"),
    166        serialized: {
    167          type: "htmlcollection",
    168          value: [
    169            {
    170              type: "node",
    171              sharedId: domElRef,
    172              value: {
    173                nodeType: 1,
    174                localName: "div",
    175                namespaceURI: "http://www.w3.org/1999/xhtml",
    176                childNodeCount: 0,
    177                attributes: {},
    178                shadowRoot: null,
    179              },
    180            },
    181          ],
    182        },
    183      },
    184    ];
    185 
    186    for (const type of REMOTE_COMPLEX_VALUES) {
    187      serializeAndAssertRemoteValue(type);
    188    }
    189  });
    190 });
    191 
    192 add_task(async function test_serializeWindow() {
    193  await loadURL(inline("<iframe>"));
    194 
    195  await runTestInContent(() => {
    196    const REMOTE_COMPLEX_VALUES = [
    197      {
    198        value: content,
    199        serialized: {
    200          type: "window",
    201          value: {
    202            context: content.browsingContext.browserId.toString(),
    203            isTopBrowsingContext: true,
    204          },
    205        },
    206      },
    207      {
    208        value: content.frames[0],
    209        serialized: {
    210          type: "window",
    211          value: {
    212            context: content.frames[0].browsingContext.id.toString(),
    213          },
    214        },
    215      },
    216      {
    217        value: content.document.querySelector("iframe").contentWindow,
    218        serialized: {
    219          type: "window",
    220          value: {
    221            context: content.document
    222              .querySelector("iframe")
    223              .contentWindow.browsingContext.id.toString(),
    224          },
    225        },
    226      },
    227    ];
    228 
    229    for (const type of REMOTE_COMPLEX_VALUES) {
    230      serializeAndAssertRemoteValue(type);
    231    }
    232  });
    233 });
    234 
    235 add_task(async function test_serializeNodeChildren() {
    236  await loadURL(inline("<div></div><iframe/>"));
    237 
    238  await runTestInContent(() => {
    239    // Add the used elements to the cache so that we know the unique reference.
    240    const bodyEl = content.document.body;
    241    const domEl = bodyEl.querySelector("div");
    242    const iframeEl = bodyEl.querySelector("iframe");
    243 
    244    const bodyElRef = nodeCache.getOrCreateNodeReference(bodyEl, seenNodeIds);
    245    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
    246    const iframeElRef = nodeCache.getOrCreateNodeReference(
    247      iframeEl,
    248      seenNodeIds
    249    );
    250 
    251    const dataSet = [
    252      {
    253        node: bodyEl,
    254        serializationOptions: {
    255          maxDomDepth: null,
    256        },
    257        serialized: {
    258          type: "node",
    259          sharedId: bodyElRef,
    260          value: {
    261            nodeType: 1,
    262            localName: "body",
    263            namespaceURI: "http://www.w3.org/1999/xhtml",
    264            childNodeCount: 2,
    265            children: [
    266              {
    267                type: "node",
    268                sharedId: domElRef,
    269                value: {
    270                  nodeType: 1,
    271                  localName: "div",
    272                  namespaceURI: "http://www.w3.org/1999/xhtml",
    273                  childNodeCount: 0,
    274                  children: [],
    275                  attributes: {},
    276                  shadowRoot: null,
    277                },
    278              },
    279              {
    280                type: "node",
    281                sharedId: iframeElRef,
    282                value: {
    283                  nodeType: 1,
    284                  localName: "iframe",
    285                  namespaceURI: "http://www.w3.org/1999/xhtml",
    286                  childNodeCount: 0,
    287                  children: [],
    288                  attributes: {},
    289                  shadowRoot: null,
    290                },
    291              },
    292            ],
    293            attributes: {},
    294            shadowRoot: null,
    295          },
    296        },
    297      },
    298      {
    299        node: bodyEl,
    300        serializationOptions: {
    301          maxDomDepth: 0,
    302        },
    303        serialized: {
    304          type: "node",
    305          sharedId: bodyElRef,
    306          value: {
    307            attributes: {},
    308            childNodeCount: 2,
    309            localName: "body",
    310            namespaceURI: "http://www.w3.org/1999/xhtml",
    311            nodeType: 1,
    312            shadowRoot: null,
    313          },
    314        },
    315      },
    316      {
    317        node: bodyEl,
    318        serializationOptions: {
    319          maxDomDepth: 1,
    320        },
    321        serialized: {
    322          type: "node",
    323          sharedId: bodyElRef,
    324          value: {
    325            attributes: {},
    326            childNodeCount: 2,
    327            children: [
    328              {
    329                type: "node",
    330                sharedId: domElRef,
    331                value: {
    332                  attributes: {},
    333                  childNodeCount: 0,
    334                  localName: "div",
    335                  namespaceURI: "http://www.w3.org/1999/xhtml",
    336                  nodeType: 1,
    337                  shadowRoot: null,
    338                },
    339              },
    340              {
    341                type: "node",
    342                sharedId: iframeElRef,
    343                value: {
    344                  attributes: {},
    345                  childNodeCount: 0,
    346                  localName: "iframe",
    347                  namespaceURI: "http://www.w3.org/1999/xhtml",
    348                  nodeType: 1,
    349                  shadowRoot: null,
    350                },
    351              },
    352            ],
    353            localName: "body",
    354            namespaceURI: "http://www.w3.org/1999/xhtml",
    355            nodeType: 1,
    356            shadowRoot: null,
    357          },
    358        },
    359      },
    360      {
    361        node: domEl,
    362        serializationOptions: {
    363          maxDomDepth: 0,
    364        },
    365        serialized: {
    366          type: "node",
    367          sharedId: domElRef,
    368          value: {
    369            attributes: {},
    370            childNodeCount: 0,
    371            localName: "div",
    372            namespaceURI: "http://www.w3.org/1999/xhtml",
    373            nodeType: 1,
    374            shadowRoot: null,
    375          },
    376        },
    377      },
    378      {
    379        node: domEl,
    380        serializationOptions: {
    381          maxDomDepth: 1,
    382        },
    383        serialized: {
    384          type: "node",
    385          sharedId: domElRef,
    386          value: {
    387            attributes: {},
    388            childNodeCount: 0,
    389            children: [],
    390            localName: "div",
    391            namespaceURI: "http://www.w3.org/1999/xhtml",
    392            nodeType: 1,
    393            shadowRoot: null,
    394          },
    395        },
    396      },
    397    ];
    398 
    399    for (const { node, serializationOptions, serialized } of dataSet) {
    400      const { maxDomDepth } = serializationOptions;
    401      info(`Checking '${node.localName}' with maxDomDepth ${maxDomDepth}`);
    402 
    403      const serializationInternalMap = new Map();
    404 
    405      const serializedValue = serialize(
    406        node,
    407        serializationOptions,
    408        "none",
    409        serializationInternalMap,
    410        realm,
    411        { nodeCache, seenNodeIds }
    412      );
    413 
    414      Assert.deepEqual(serializedValue, serialized, "Got expected structure");
    415    }
    416  });
    417 });
    418 
    419 add_task(async function test_serializeNodeEmbeddedWithin() {
    420  await loadURL(inline("<div>"));
    421 
    422  await runTestInContent(() => {
    423    // Add the used elements to the cache so that we know the unique reference.
    424    const bodyEl = content.document.body;
    425    const domEl = bodyEl.querySelector("div");
    426    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
    427 
    428    const dataSet = [
    429      {
    430        embedder: "array",
    431        wrapper: node => [node],
    432        serialized: {
    433          type: "array",
    434          value: [
    435            {
    436              type: "node",
    437              sharedId: domElRef,
    438              value: {
    439                attributes: {},
    440                childNodeCount: 0,
    441                localName: "div",
    442                namespaceURI: "http://www.w3.org/1999/xhtml",
    443                nodeType: 1,
    444                shadowRoot: null,
    445              },
    446            },
    447          ],
    448        },
    449      },
    450      {
    451        embedder: "map",
    452        wrapper: node => {
    453          const map = new Map();
    454          map.set(node, "elem");
    455          return map;
    456        },
    457        serialized: {
    458          type: "map",
    459          value: [
    460            [
    461              {
    462                type: "node",
    463                sharedId: domElRef,
    464                value: {
    465                  attributes: {},
    466                  childNodeCount: 0,
    467                  localName: "div",
    468                  namespaceURI: "http://www.w3.org/1999/xhtml",
    469                  nodeType: 1,
    470                  shadowRoot: null,
    471                },
    472              },
    473              {
    474                type: "string",
    475                value: "elem",
    476              },
    477            ],
    478          ],
    479        },
    480      },
    481      {
    482        embedder: "map",
    483        wrapper: node => {
    484          const map = new Map();
    485          map.set("elem", node);
    486          return map;
    487        },
    488        serialized: {
    489          type: "map",
    490          value: [
    491            [
    492              "elem",
    493              {
    494                type: "node",
    495                sharedId: domElRef,
    496                value: {
    497                  attributes: {},
    498                  childNodeCount: 0,
    499                  localName: "div",
    500                  namespaceURI: "http://www.w3.org/1999/xhtml",
    501                  nodeType: 1,
    502                  shadowRoot: null,
    503                },
    504              },
    505            ],
    506          ],
    507        },
    508      },
    509      {
    510        embedder: "object",
    511        wrapper: node => ({ elem: node }),
    512        serialized: {
    513          type: "object",
    514          value: [
    515            [
    516              "elem",
    517              {
    518                type: "node",
    519                sharedId: domElRef,
    520                value: {
    521                  attributes: {},
    522                  childNodeCount: 0,
    523                  localName: "div",
    524                  namespaceURI: "http://www.w3.org/1999/xhtml",
    525                  nodeType: 1,
    526                  shadowRoot: null,
    527                },
    528              },
    529            ],
    530          ],
    531        },
    532      },
    533      {
    534        embedder: "set",
    535        wrapper: node => {
    536          const set = new Set();
    537          set.add(node);
    538          return set;
    539        },
    540        serialized: {
    541          type: "set",
    542          value: [
    543            {
    544              type: "node",
    545              sharedId: domElRef,
    546              value: {
    547                attributes: {},
    548                childNodeCount: 0,
    549                localName: "div",
    550                namespaceURI: "http://www.w3.org/1999/xhtml",
    551                nodeType: 1,
    552                shadowRoot: null,
    553              },
    554            },
    555          ],
    556        },
    557      },
    558    ];
    559 
    560    for (const { embedder, wrapper, serialized } of dataSet) {
    561      info(`Checking embedding node within ${embedder}`);
    562 
    563      const serializationInternalMap = new Map();
    564 
    565      const serializedValue = serialize(
    566        wrapper(domEl),
    567        { maxDomDepth: 0 },
    568        "none",
    569        serializationInternalMap,
    570        realm,
    571        { nodeCache }
    572      );
    573 
    574      Assert.deepEqual(serializedValue, serialized, "Got expected structure");
    575    }
    576  });
    577 });
    578 
    579 add_task(async function test_serializeShadowRoot() {
    580  await runTestInContent(() => {
    581    for (const mode of ["open", "closed"]) {
    582      info(`Checking shadow root with mode '${mode}'`);
    583      const customElement = content.document.createElement(
    584        `${mode}-custom-element`
    585      );
    586      const insideShadowRootElement = content.document.createElement("input");
    587      content.document.body.appendChild(customElement);
    588      const shadowRoot = customElement.attachShadow({ mode });
    589      shadowRoot.appendChild(insideShadowRootElement);
    590 
    591      // Add the used elements to the cache so that we know the unique reference.
    592      const customElementRef = nodeCache.getOrCreateNodeReference(
    593        customElement,
    594        seenNodeIds
    595      );
    596      const shadowRootRef = nodeCache.getOrCreateNodeReference(
    597        shadowRoot,
    598        seenNodeIds
    599      );
    600      const insideShadowRootElementRef = nodeCache.getOrCreateNodeReference(
    601        insideShadowRootElement,
    602        seenNodeIds
    603      );
    604 
    605      const dataSet = [
    606        {
    607          node: customElement,
    608          serializationOptions: {
    609            maxDomDepth: 1,
    610          },
    611          serialized: {
    612            type: "node",
    613            sharedId: customElementRef,
    614            value: {
    615              attributes: {},
    616              childNodeCount: 0,
    617              children: [],
    618              localName: `${mode}-custom-element`,
    619              namespaceURI: "http://www.w3.org/1999/xhtml",
    620              nodeType: 1,
    621              shadowRoot: {
    622                sharedId: shadowRootRef,
    623                type: "node",
    624                value: {
    625                  childNodeCount: 1,
    626                  mode,
    627                  nodeType: 11,
    628                },
    629              },
    630            },
    631          },
    632        },
    633        {
    634          node: customElement,
    635          serializationOptions: {
    636            includeShadowTree: "open",
    637            maxDomDepth: 1,
    638          },
    639          serialized: {
    640            type: "node",
    641            sharedId: customElementRef,
    642            value: {
    643              attributes: {},
    644              childNodeCount: 0,
    645              children: [],
    646              localName: `${mode}-custom-element`,
    647              namespaceURI: "http://www.w3.org/1999/xhtml",
    648              nodeType: 1,
    649              shadowRoot: {
    650                sharedId: shadowRootRef,
    651                type: "node",
    652                value: {
    653                  childNodeCount: 1,
    654                  mode,
    655                  nodeType: 11,
    656                  ...(mode === "open"
    657                    ? {
    658                        children: [
    659                          {
    660                            type: "node",
    661                            sharedId: insideShadowRootElementRef,
    662                            value: {
    663                              nodeType: 1,
    664                              localName: "input",
    665                              namespaceURI: "http://www.w3.org/1999/xhtml",
    666                              childNodeCount: 0,
    667                              attributes: {},
    668                              shadowRoot: null,
    669                            },
    670                          },
    671                        ],
    672                      }
    673                    : {}),
    674                },
    675              },
    676            },
    677          },
    678        },
    679        {
    680          node: customElement,
    681          serializationOptions: {
    682            includeShadowTree: "all",
    683            maxDomDepth: 1,
    684          },
    685          serialized: {
    686            type: "node",
    687            sharedId: customElementRef,
    688            value: {
    689              attributes: {},
    690              childNodeCount: 0,
    691              children: [],
    692              localName: `${mode}-custom-element`,
    693              namespaceURI: "http://www.w3.org/1999/xhtml",
    694              nodeType: 1,
    695              shadowRoot: {
    696                sharedId: shadowRootRef,
    697                type: "node",
    698                value: {
    699                  childNodeCount: 1,
    700                  mode,
    701                  nodeType: 11,
    702                  children: [
    703                    {
    704                      type: "node",
    705                      sharedId: insideShadowRootElementRef,
    706                      value: {
    707                        nodeType: 1,
    708                        localName: "input",
    709                        namespaceURI: "http://www.w3.org/1999/xhtml",
    710                        childNodeCount: 0,
    711                        attributes: {},
    712                        shadowRoot: null,
    713                      },
    714                    },
    715                  ],
    716                },
    717              },
    718            },
    719          },
    720        },
    721      ];
    722 
    723      for (const { node, serializationOptions, serialized } of dataSet) {
    724        const { maxDomDepth, includeShadowTree } = serializationOptions;
    725        info(
    726          `Checking shadow root with maxDomDepth ${maxDomDepth} and includeShadowTree ${includeShadowTree}`
    727        );
    728 
    729        const serializationInternalMap = new Map();
    730 
    731        const serializedValue = serialize(
    732          node,
    733          serializationOptions,
    734          "none",
    735          serializationInternalMap,
    736          realm,
    737          { nodeCache }
    738        );
    739 
    740        Assert.deepEqual(serializedValue, serialized, "Got expected structure");
    741      }
    742    }
    743  });
    744 });
    745 
    746 add_task(async function test_serializeNodeSharedId() {
    747  await loadURL(inline("<div>"));
    748 
    749  await runTestInContent(() => {
    750    const domEl = content.document.querySelector("div");
    751 
    752    // Already add the domEl to the cache so that we know the unique reference.
    753    const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds);
    754 
    755    const serializedValue = serialize(
    756      domEl,
    757      { maxDomDepth: 0 },
    758      "root",
    759      serializationInternalMap,
    760      realm,
    761      { nodeCache, seenNodeIds }
    762    );
    763 
    764    Assert.equal(nodeCache.size, 1, "No additional reference added");
    765    Assert.equal(serializedValue.sharedId, domElRef);
    766    Assert.notEqual(serializedValue.handle, domElRef);
    767  });
    768 });
    769 
    770 function runTestInContent(callback) {
    771  return SpecialPowers.spawn(
    772    gBrowser.selectedBrowser,
    773    [callback.toString()],
    774    async callback => {
    775      const { NodeCache } = ChromeUtils.importESModule(
    776        "chrome://remote/content/shared/webdriver/NodeCache.sys.mjs"
    777      );
    778      const { Realm, WindowRealm } = ChromeUtils.importESModule(
    779        "chrome://remote/content/shared/Realm.sys.mjs"
    780      );
    781      const { deserialize, serialize, setDefaultSerializationOptions } =
    782        ChromeUtils.importESModule(
    783          "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs"
    784        );
    785 
    786      function assertInternalIds(serializationInternalMap, amount) {
    787        const remoteValuesWithInternalIds = Array.from(
    788          serializationInternalMap.values()
    789        ).filter(remoteValue => !!remoteValue.internalId);
    790 
    791        Assert.equal(
    792          remoteValuesWithInternalIds.length,
    793          amount,
    794          "Got expected amount of internalIds in serializationInternalMap"
    795        );
    796      }
    797 
    798      const nodeCache = new NodeCache();
    799      const seenNodeIds = new Map();
    800      const realm = new WindowRealm(content);
    801      const serializationInternalMap = new Map();
    802 
    803      function serializeAndAssertRemoteValue(remoteValue) {
    804        const { value, serialized } = remoteValue;
    805        const serializationOptionsWithDefaults =
    806          setDefaultSerializationOptions();
    807        const serializationInternalMapWithNone = new Map();
    808 
    809        info(`Checking '${serialized.type}' with none ownershipType`);
    810 
    811        const serializedValue = serialize(
    812          value,
    813          serializationOptionsWithDefaults,
    814          "none",
    815          serializationInternalMapWithNone,
    816          realm,
    817          { nodeCache, seenNodeIds }
    818        );
    819 
    820        assertInternalIds(serializationInternalMapWithNone, 0);
    821        Assert.deepEqual(serialized, serializedValue, "Got expected structure");
    822 
    823        info(`Checking '${serialized.type}' with root ownershipType`);
    824        const serializationInternalMapWithRoot = new Map();
    825        const serializedWithRoot = serialize(
    826          value,
    827          serializationOptionsWithDefaults,
    828          "root",
    829          serializationInternalMapWithRoot,
    830          realm,
    831          { nodeCache, seenNodeIds }
    832        );
    833 
    834        assertInternalIds(serializationInternalMapWithRoot, 0);
    835        Assert.equal(
    836          typeof serializedWithRoot.handle,
    837          "string",
    838          "Got a handle property"
    839        );
    840        Assert.deepEqual(
    841          Object.assign({}, serialized, { handle: serializedWithRoot.handle }),
    842          serializedWithRoot,
    843          "Got expected structure, plus a generated handle id"
    844        );
    845      }
    846 
    847      // eslint-disable-next-line no-eval
    848      eval(`(${callback})()`);
    849    }
    850  );
    851 }