summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/test/browser/browser_webconsole_bidi_string_isolation.js
blob: cfceff96af40345dd2012d49ed5b8983e1b8c36d (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
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const TEST_URI = "data:text/html;charset=utf8,<!DOCTYPE html>Bidi strings";
const rtlOverride = "\u202e";

add_task(async function () {
  const hud = await openNewTabAndConsole(TEST_URI);
  const browser = gBrowser.selectedBrowser;

  /* eslint-disable-next-line no-shadow */
  await SpecialPowers.spawn(browser, [rtlOverride], rtlOverride => {
    const { console } = content.wrappedJSObject;

    console.log(Symbol(rtlOverride + "msg01"));
    console.log([rtlOverride + "msg02"]);
    console.log({ p: rtlOverride + "msg03" });
    console.log({ [rtlOverride + "msg04"]: null });
    console.log(new Set([rtlOverride + "msg05"]));
    console.log(new Map([[rtlOverride + "msg06", null]]));
    console.log(new Map([[null, rtlOverride + "msg07"]]));

    const parser = content.document.createElement("div");
    parser.innerHTML = `
      <div data-test="${rtlOverride}msg08"></div>
      <div data-${rtlOverride}="msg09"></div>
      <div-${rtlOverride} msg10></div-${rtlOverride}>
    `;
    for (const child of parser.children) {
      console.log(child);
    }
  });

  const texts = [
    `Symbol("${rtlOverride}msg01")`,
    `Array [ "${rtlOverride}msg02" ]`,
    `Object { p: "${rtlOverride}msg03" }`,
    `Object { "${rtlOverride}msg04": null }`,
    `Set [ "${rtlOverride}msg05" ]`,
    `Map { "${rtlOverride}msg06" → null }`,
    `Map { null → "${rtlOverride}msg07" }`,
    `<div data-test="${rtlOverride}msg08">`,
    `<div data-${rtlOverride}="msg09">`,
    `<div-${rtlOverride} msg10="">`,
  ];
  for (let i = 0; i < texts.length; ++i) {
    const msgId = "msg" + String(i + 1).padStart(2, "0");
    const message = await waitFor(() => findConsoleAPIMessage(hud, msgId));
    const objectBox = message.querySelector(".objectBox");
    is(objectBox.textContent, texts[i], "Should have all the relevant text");
    checkRects(objectBox);
  }
});

function getBoundingClientRect(node) {
  if (node.nodeType === Node.ELEMENT_NODE) {
    return node.getBoundingClientRect();
  }
  // There is no Node.getBoundingClientRect, use a Range instead.
  const range = document.createRange();
  range.selectNode(node);
  return range.getBoundingClientRect();
}

/**
 * The console prints data build from external strings. They can contain
 * characters that change the directionality of the text. For example, RTL
 * characters will flow right to left. However, this should be isolated to
 * prevent one string from mangling how another one is rendered.
 * This function uses getBoundingClientRect() to check that the nodes, as a
 * whole, flow LTR (even if the characters in the node flow RTL).
 * The bidi algorithm happens at layout time, so we need to check the rects,
 * DOM operations like textContent would be useless.
 */
function checkRects(node, parentRect = getBoundingClientRect(node)) {
  let prevRect;
  for (const child of node.childNodes) {
    const rect = getBoundingClientRect(child);
    Assert.greaterOrEqual(
      rect.x,
      parentRect.x,
      "Rect should start inside parent"
    );
    Assert.lessOrEqual(
      rect.x + rect.width,
      parentRect.x + parentRect.width,
      "Rect should end inside parent"
    );
    if (prevRect) {
      Assert.greaterOrEqual(
        rect.x,
        prevRect.x + prevRect.width,
        "Rect should start after previous one"
      );
    }
    prevRect = rect;
    checkRects(child, rect);
  }
}