browser_webconsole_bidi_string_isolation.js (3499B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const TEST_URI = "data:text/html;charset=utf8,<!DOCTYPE html>Bidi strings"; 7 const rtlOverride = "\u202e"; 8 9 add_task(async function () { 10 const hud = await openNewTabAndConsole(TEST_URI); 11 const browser = gBrowser.selectedBrowser; 12 13 /* eslint-disable-next-line no-shadow */ 14 await SpecialPowers.spawn(browser, [rtlOverride], rtlOverride => { 15 const { console } = content.wrappedJSObject; 16 17 console.log(Symbol(rtlOverride + "msg01")); 18 console.log([rtlOverride + "msg02"]); 19 console.log({ p: rtlOverride + "msg03" }); 20 console.log({ [rtlOverride + "msg04"]: null }); 21 console.log(new Set([rtlOverride + "msg05"])); 22 console.log(new Map([[rtlOverride + "msg06", null]])); 23 console.log(new Map([[null, rtlOverride + "msg07"]])); 24 25 const parser = content.document.createElement("div"); 26 parser.innerHTML = ` 27 <div data-test="${rtlOverride}msg08"></div> 28 <div data-${rtlOverride}="msg09"></div> 29 <div-${rtlOverride} msg10></div-${rtlOverride}> 30 `; 31 for (const child of parser.children) { 32 console.log(child); 33 } 34 }); 35 36 const texts = [ 37 `Symbol("${rtlOverride}msg01")`, 38 `Array [ "${rtlOverride}msg02" ]`, 39 `Object { p: "${rtlOverride}msg03" }`, 40 `Object { "${rtlOverride}msg04": null }`, 41 `Set [ "${rtlOverride}msg05" ]`, 42 `Map { "${rtlOverride}msg06" → null }`, 43 `Map { null → "${rtlOverride}msg07" }`, 44 `<div data-test="${rtlOverride}msg08">`, 45 `<div data-${rtlOverride}="msg09">`, 46 `<div-${rtlOverride} msg10="">`, 47 ]; 48 for (let i = 0; i < texts.length; ++i) { 49 const msgId = "msg" + String(i + 1).padStart(2, "0"); 50 const message = await waitFor(() => findConsoleAPIMessage(hud, msgId)); 51 const objectBox = message.querySelector(".objectBox"); 52 is(objectBox.textContent, texts[i], "Should have all the relevant text"); 53 checkRects(objectBox); 54 } 55 }); 56 57 function getBoundingClientRect(node) { 58 if (node.nodeType === Node.ELEMENT_NODE) { 59 return node.getBoundingClientRect(); 60 } 61 // There is no Node.getBoundingClientRect, use a Range instead. 62 const range = document.createRange(); 63 range.selectNode(node); 64 return range.getBoundingClientRect(); 65 } 66 67 /** 68 * The console prints data build from external strings. They can contain 69 * characters that change the directionality of the text. For example, RTL 70 * characters will flow right to left. However, this should be isolated to 71 * prevent one string from mangling how another one is rendered. 72 * This function uses getBoundingClientRect() to check that the nodes, as a 73 * whole, flow LTR (even if the characters in the node flow RTL). 74 * The bidi algorithm happens at layout time, so we need to check the rects, 75 * DOM operations like textContent would be useless. 76 */ 77 function checkRects(node, parentRect = getBoundingClientRect(node)) { 78 let prevRect; 79 for (const child of node.childNodes) { 80 const rect = getBoundingClientRect(child); 81 Assert.greaterOrEqual( 82 rect.x, 83 parentRect.x, 84 "Rect should start inside parent" 85 ); 86 Assert.lessOrEqual( 87 rect.x + rect.width, 88 parentRect.x + parentRect.width, 89 "Rect should end inside parent" 90 ); 91 if (prevRect) { 92 Assert.greaterOrEqual( 93 rect.x, 94 prevRect.x + prevRect.width, 95 "Rect should start after previous one" 96 ); 97 } 98 prevRect = rect; 99 checkRects(child, rect); 100 } 101 }