tor-browser

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

test_range_bounds.html (15106B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <!--
      4 https://bugzilla.mozilla.org/show_bug.cgi?id=421640
      5 -->
      6 <head>
      7  <title>Test for Bug 396392</title>
      8  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      9  <script src="/tests/SimpleTest/SimpleTest.js"></script>
     10  <script src="/tests/SimpleTest/EventUtils.js"></script>
     11  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
     12 </head>
     13 <body>
     14 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396392">Mozilla Bug Range getClientRects and getBoundingClientRect</a>
     15 <div id="content" style="font-family:monospace;font-size:12px;width:100px">
     16 <p>000000<span>0</span></p><div>00000<span>0</span></div><p>0000<span>0000</span>0000</p><div><span>000000000000 00000000000000 000000</span></div><div>000000000000 00000000000003 100305</div>
     17 </div>
     18 <div id="mixeddir" style="font-family:monospace;font-size:12px;width:100px"><span>english <bdo id="bdo" dir="rtl">rtl-overide english</bdo> word</span></div>
     19 <div id="mixeddir2" style="font-family:monospace;font-size:12px"><span>english <bdo id="bdo2" dir="rtl">rtl-override english</bdo> word</span></div>
     20 <pre id="test">
     21 <script class="testbody" type="text/javascript">
     22 
     23 var isLTR = true;
     24 var isTransformed = false;
     25 
     26 function annotateName(name) {
     27  return (isLTR ? 'isLTR ' : 'isRTL ') +
     28    (isTransformed ? 'transformed ' : '') + name;
     29 }
     30 
     31 function isEmptyRect(rect, name) {
     32  name = annotateName(name);
     33  is(rect.left, 0, name+'empty rect should have left = 0');
     34  is(rect.right, 0, name+'empty rect should have right = 0');
     35  is(rect.top, 0, name+'empty rect should have top = 0');
     36  is(rect.bottom, 0, name+'empty rect should have bottom = 0');
     37  is(rect.width, 0, name+'empty rect should have width = 0');
     38  is(rect.height, 0, name+'empty rect should have height = 0');
     39 }
     40 
     41 function getTextBoundingClientRect(node) {
     42  const quads = node.getBoxQuads()[0];
     43  return DOMRect.fromRect({
     44    x: quads.p1.x,
     45    y: quads.p1.y,
     46    width: quads.p2.x - quads.p1.x,
     47    height: quads.p3.y - quads.p2.y
     48  });
     49 }
     50 
     51 function sortRectList(rectlist) {
     52  return Array.prototype.slice.call(rectlist, 0).sort(function(a, b) {
     53    return a.top - b.top || a.left - b.left;
     54  });
     55 }
     56 
     57 function isEmptyRectList(rectlist, name) {
     58  name = annotateName(name);
     59  is(rectlist.length, 0, name + 'empty rectlist should have zero rects');
     60 }
     61 
     62 // round coordinates to the nearest 1/256 of a pixel
     63 function roundCoord(x) {
     64  return Math.round(x * 256) / 256;
     65 }
     66 
     67 function _getRect(r) {
     68  if (r.length) //array
     69    return "{left:"+roundCoord(r[0])+",right:"+roundCoord(r[1])+
     70      ",top:"   +roundCoord(r[2])+",bottom:"+roundCoord(r[3])+
     71      ",width:"+roundCoord(r[4])+",height:"+roundCoord(r[5])+"}";
     72  else
     73    return "{left:"+roundCoord(r.left)+",right:"+roundCoord(r.right)+
     74      ",top:"+roundCoord(r.top)+",bottom:"+roundCoord(r.bottom)+
     75      ",width:"+roundCoord(r.width)+",height:"+roundCoord(r.height)+"}";
     76 }
     77 
     78 function runATest(obj) {
     79  var range = document.createRange();
     80  try {
     81    range.setStart(obj.range[0],obj.range[1]);
     82    if (obj.range.length>2) {
     83       range.setEnd(obj.range[2]||obj.range[0], obj.range[3]);
     84    }
     85    //test getBoundingClientRect()
     86    var rect = range.getBoundingClientRect();
     87    var testname = 'range.getBoundingClientRect for ' + obj.name;
     88    if (obj.rect) {
     89      is(_getRect(rect),_getRect(obj.rect), annotateName(testname));
     90    } else {
     91      isEmptyRect(rect,testname+": ");
     92    }
     93    //test getClientRects()
     94    var rectlist = range.getClientRects();
     95    testname = 'range.getClientRects for ' + obj.name;
     96    if (!obj.rectList) {
     97      //rectList is not specified, use obj.rect to figure out rectList
     98      obj.rectList = obj.rect?[obj.rect]:[];
     99    }
    100    if (!obj.rectList.length) {
    101      isEmptyRectList(rectlist, testname+": ");
    102    } else {
    103      is(rectlist.length, obj.rectList.length,
    104         annotateName(testname+' should return '+obj.rectList.length+' rects.'));
    105      if(!obj.rectList.forEach){
    106        //convert RectList to a real array
    107        obj.rectList=Array.prototype.slice.call(obj.rectList, 0);
    108      }
    109      if (obj.mustSortBeforeComparing) {
    110        rectlist = sortRectList(rectlist);
    111      }
    112      obj.rectList.forEach(function(r,i) {
    113        is(_getRect(rectlist[i]),_getRect(r),
    114           annotateName(testname+": item at "+i));
    115      });
    116    }
    117  } finally {
    118    range.detach();
    119  }
    120 }
    121 /** Test for Bug 396392 */
    122 function doTest(){
    123  var root = document.getElementById('content');
    124  var firstP = root.firstElementChild, spanInFirstP = firstP.childNodes[1],
    125    firstDiv = root.childNodes[2], spanInFirstDiv = firstDiv.childNodes[1],
    126    secondP = root.childNodes[3], spanInSecondP = secondP.childNodes[1],
    127    secondDiv = root.childNodes[4], spanInSecondDiv = secondDiv.firstChild,
    128    thirdDiv = root.childNodes[5];
    129  var firstPRect = firstP.getBoundingClientRect(),
    130    spanInFirstPRect = spanInFirstP.getBoundingClientRect(),
    131    textInFirstPRect = getTextBoundingClientRect(firstP.firstChild),
    132    textInSpanInFirstPRect = getTextBoundingClientRect(spanInFirstP.firstChild),
    133    firstDivRect = firstDiv.getBoundingClientRect(),
    134    textInFirstDivRect = getTextBoundingClientRect(firstDiv.firstChild),
    135    spanInFirstDivRect = spanInFirstDiv.getBoundingClientRect(),
    136    textInSpanInFirstDivRect = getTextBoundingClientRect(spanInFirstDiv.firstChild),
    137    secondPRect = secondP.getBoundingClientRect(),
    138    secondDivRect = secondDiv.getBoundingClientRect(),
    139    spanInSecondPRect = spanInSecondP.getBoundingClientRect(),
    140    textInSpanInSecondPRect = getTextBoundingClientRect(spanInSecondP.firstChild),
    141    spanInSecondDivRect = spanInSecondDiv.getBoundingClientRect(),
    142    spanInSecondDivRectList = spanInSecondDiv.getClientRects();
    143  var widthPerchar = spanInSecondPRect.width / spanInSecondP.firstChild.length;
    144  var testcases = [
    145    {name:'nodesNotInDocument', range:[document.createTextNode('abc'), 1],
    146      rect:null},
    147    {name:'collapsedInBlockNode', range:[firstP, 2], rect:null},
    148    {name:'collapsedAtBeginningOfTextNode', range:[firstP.firstChild, 0],
    149      rect:[spanInFirstPRect.left - 6 * widthPerchar,
    150      spanInFirstPRect.left - 6 * widthPerchar, spanInFirstPRect.top,
    151      spanInFirstPRect.bottom, 0, spanInFirstPRect.height]},
    152    {name:'collapsedWithinTextNode', range:[firstP.firstChild, 1],
    153      rect:[spanInFirstPRect.left  - 5 * widthPerchar,
    154        spanInFirstPRect.left  - 5 * widthPerchar,
    155        spanInFirstPRect.top, spanInFirstPRect.bottom, 0, spanInFirstPRect.height]},
    156    {name:'collapsedAtEndOfTextNode', range:[firstP.firstChild, 6],
    157      rect:[spanInFirstPRect.left, spanInFirstPRect.left,
    158        spanInFirstPRect.top, spanInFirstPRect.bottom, 0, spanInFirstPRect.height]},
    159    {name:'singleBlockNode', range:[root, 1, root, 2], rect:firstPRect,
    160        rectList:[firstPRect, textInFirstPRect, spanInFirstPRect]},
    161    {name:'twoBlockNodes', range:[root, 1, root, 3],
    162      rect:[firstPRect.left, firstPRect.right, firstPRect.top,
    163        firstDivRect.bottom, firstPRect.width,
    164        firstDivRect.bottom - firstPRect.top],
    165      rectList:[firstPRect, textInFirstPRect, textInSpanInFirstPRect,
    166        firstDivRect, textInFirstDivRect, textInSpanInFirstDivRect]},
    167    {name:'endOfTextNodeToEndOfAnotherTextNodeInAnotherBlock',
    168      range:[spanInFirstP.firstChild, 1, firstDiv.firstChild, 5],
    169      rect:[spanInFirstDivRect.left - 5*widthPerchar, spanInFirstDivRect.left,
    170        spanInFirstDivRect.top, spanInFirstDivRect.bottom, 5 * widthPerchar,
    171        spanInFirstDivRect.height]},
    172    {name:'startOfTextNodeToStartOfAnotherTextNodeInAnotherBlock',
    173      range:[spanInFirstP.firstChild, 0, firstDiv.firstChild, 0],
    174      rect:[spanInFirstPRect.left, spanInFirstPRect.left + widthPerchar, spanInFirstPRect.top,
    175        spanInFirstPRect.bottom, widthPerchar, spanInFirstPRect.height]},
    176    {name:'endPortionOfATextNode', range:[firstP.firstChild, 3,
    177        firstP.firstChild, 6],
    178      rect:[spanInFirstPRect.left - 3*widthPerchar, spanInFirstPRect.left,
    179        spanInFirstPRect.top, spanInFirstPRect.bottom, 3*widthPerchar, spanInFirstPRect.height]},
    180    {name:'startPortionOfATextNode', range:[firstP.firstChild, 0,
    181        firstP.firstChild, 3],
    182      rect:[spanInFirstPRect.left - 6*widthPerchar,
    183        spanInFirstPRect.left - 3*widthPerchar, spanInFirstPRect.top,
    184        spanInFirstPRect.bottom, 3 * widthPerchar, spanInFirstPRect.height]},
    185    {name:'spanTextNodes', range:[secondP.firstChild, 1, secondP.lastChild, 1],
    186      rect:[spanInSecondPRect.left - 3*widthPerchar, spanInSecondPRect.right +
    187        widthPerchar, spanInSecondPRect.top, spanInSecondPRect.bottom,
    188        spanInSecondPRect.width + 4*widthPerchar, spanInSecondPRect.height],
    189      rectList:[[spanInSecondPRect.left - 3*widthPerchar, spanInSecondPRect.left,
    190        spanInSecondPRect.top, spanInSecondPRect.bottom, 3 * widthPerchar,
    191        spanInSecondPRect.height],
    192 spanInSecondPRect, textInSpanInSecondPRect,
    193 [spanInSecondPRect.right, spanInSecondPRect.right + widthPerchar,
    194          spanInSecondPRect.top, spanInSecondPRect.bottom, widthPerchar,
    195          spanInSecondPRect.height]]}
    196  ];
    197  testcases.forEach(runATest);
    198 
    199  // testcases that have different ranges in LTR and RTL
    200  var directionDependentTestcases;
    201  if (isLTR) {
    202    directionDependentTestcases = [
    203      {name:'spanAcrossLines',range:[spanInSecondDiv.firstChild, 1, spanInSecondDiv.firstChild, 30],
    204       rect: spanInSecondDivRect,
    205       rectList:[[spanInSecondDivRectList[0].left+widthPerchar,
    206        spanInSecondDivRectList[0].right, spanInSecondDivRectList[0].top,
    207 spanInSecondDivRectList[0].bottom, spanInSecondDivRectList[0].width - widthPerchar,
    208 spanInSecondDivRectList[0].height],
    209 spanInSecondDivRectList[1],
    210 [spanInSecondDivRectList[2].left,
    211 spanInSecondDivRectList[2].right - 4 * widthPerchar, spanInSecondDivRectList[2].top,
    212 spanInSecondDivRectList[2].bottom,
    213 spanInSecondDivRectList[2].width - 4 * widthPerchar,
    214 spanInSecondDivRectList[2].height]]},
    215      {name:'textAcrossLines',range:[thirdDiv.firstChild, 13, thirdDiv.firstChild, 28],
    216        rect: [spanInSecondDivRectList[1].left, spanInSecondDivRectList[1].right,
    217          spanInSecondDivRectList[1].top + secondDivRect.height,
    218          spanInSecondDivRectList[1].bottom + secondDivRect.height,
    219          spanInSecondDivRectList[1].width, spanInSecondDivRectList[1].height]}
    220    ];
    221  } else {
    222    directionDependentTestcases = [
    223      {name:'spanAcrossLines',range:[spanInSecondDiv.firstChild, 1, spanInSecondDiv.firstChild, 30],
    224       rect: spanInSecondDivRect,
    225       rectList:[[spanInSecondDivRectList[0].left+widthPerchar,
    226        spanInSecondDivRectList[0].right, spanInSecondDivRectList[0].top,
    227 spanInSecondDivRectList[0].bottom, spanInSecondDivRectList[0].width - widthPerchar,
    228 spanInSecondDivRectList[0].height],
    229 spanInSecondDivRectList[1],
    230 spanInSecondDivRectList[2],
    231 spanInSecondDivRectList[3],
    232 [spanInSecondDivRectList[4].left,
    233 spanInSecondDivRectList[4].right - 4 * widthPerchar,
    234        spanInSecondDivRectList[4].top,
    235 spanInSecondDivRectList[4].bottom,
    236 spanInSecondDivRectList[4].width - 4 * widthPerchar,
    237 spanInSecondDivRectList[4].height]]},
    238      {name:'textAcrossLines',range:[thirdDiv.firstChild, 13, thirdDiv.firstChild, 28],
    239        rect: [spanInSecondDivRectList[2].left, spanInSecondDivRectList[2].right,
    240          spanInSecondDivRectList[2].top + secondDivRect.height,
    241          spanInSecondDivRectList[2].bottom + secondDivRect.height,
    242        spanInSecondDivRectList[2].width, spanInSecondDivRectList[2].height],
    243       rectList:[[spanInSecondDivRectList[2].left, spanInSecondDivRectList[2].right,
    244          spanInSecondDivRectList[2].top + secondDivRect.height,
    245          spanInSecondDivRectList[2].bottom + secondDivRect.height,
    246          spanInSecondDivRectList[2].width, spanInSecondDivRectList[2].height],
    247          [spanInSecondDivRectList[2].left, spanInSecondDivRectList[2].left,
    248          spanInSecondDivRectList[2].top + secondDivRect.height,
    249          spanInSecondDivRectList[2].bottom + secondDivRect.height,
    250          0, spanInSecondDivRectList[2].height]]}
    251     ];
    252  }
    253  directionDependentTestcases.forEach(runATest);
    254 }
    255 function testMixedDir(){
    256  var root = document.getElementById('mixeddir');
    257  var bdo = document.getElementById('bdo');
    258  var firstSpan = root.firstElementChild, firstSpanRect=firstSpan.getBoundingClientRect(),
    259      firstSpanWithInnerTextRectList = Array.from(firstSpan.getClientRects());
    260      firstSpanWithInnerTextRectList.push(...bdo.getClientRects());
    261 
    262  // Depending on the font rendering, the order of the rects composing the bdo
    263  // element may vary. We need to sort the list of rects before comparing it to
    264  // the expected list.
    265      firstSpanWithInnerTextRectList = sortRectList(firstSpanWithInnerTextRectList);
    266  runATest({name:'mixeddir',range:[firstSpan.firstChild,0,firstSpan.lastChild,firstSpan.lastChild.length],
    267             rect: firstSpanRect, rectList:firstSpanWithInnerTextRectList, mustSortBeforeComparing: true});
    268 
    269  root = document.getElementById('mixeddir2');
    270  firstSpan = root.firstElementChild;
    271  firstSpanRect = firstSpan.getBoundingClientRect();
    272  bdo = document.getElementById('bdo2');
    273  bdoRect=bdo.getBoundingClientRect();
    274  var widthPerChar = bdoRect.width / bdo.firstChild.length;
    275  runATest({name:'mixeddirPartial', range:[firstSpan.firstChild, 3,
    276 				   bdo.firstChild, 7],
    277 rect: [firstSpanRect.left + 3*widthPerChar, bdoRect.right,
    278        bdoRect.top, bdoRect.bottom,
    279        (firstSpan.firstChild.length + bdo.firstChild.length - 3) *
    280         widthPerChar,
    281        bdoRect.height],
    282 rectList:[[firstSpanRect.left + 3*widthPerChar,
    283 	   bdoRect.left,
    284 	   firstSpanRect.top, firstSpanRect.bottom,
    285 	   (firstSpan.firstChild.length - 3) * widthPerChar,
    286 	   firstSpanRect.height],
    287 	  [bdoRect.right - 7 * widthPerChar, bdoRect.right,
    288 	   bdoRect.top, bdoRect.bottom,
    289 	   7*widthPerChar, bdoRect.height]]});
    290 }
    291 
    292 function testShadowDOM() {
    293  var ifr = document.createElement("iframe");
    294  document.body.appendChild(ifr);
    295  var doc = ifr.contentDocument;
    296  var d = doc.createElement("div");
    297  var sr = d.attachShadow({mode: "open"});
    298  sr.innerHTML = "<div>inside shadow DOM</div>";
    299  doc.body.appendChild(d);
    300  var r = new ifr.contentWindow.Range();
    301  r.selectNode(sr.firstChild);
    302  var rect = r.getBoundingClientRect();
    303  isnot(rect.width, 0, "Div element inside shadow shouldn't have zero size.");
    304  isnot(rect.height, 0, "Div element inside shadow shouldn't have zero size.");
    305 }
    306 
    307 async function test(){
    308  // We use getBoxQuads to get some text nodes bounding rects.
    309  await SpecialPowers.pushPrefEnv({"set": [["layout.css.getBoxQuads.enabled", true]]});
    310 
    311  //test ltr
    312  doTest();
    313 
    314  //test rtl
    315  isLTR = false;
    316  var root = document.getElementById('content');
    317  root.dir = 'rtl';
    318  doTest();
    319  isLTR = true;
    320  root.dir = 'ltr';
    321 
    322  testMixedDir();
    323 
    324  //test transforms
    325  isTransformed = true;
    326  root.style.transform = "translate(30px,50px)";
    327  doTest();
    328 
    329  testShadowDOM();
    330  SimpleTest.finish();
    331 }
    332 
    333 window.onload = function() {
    334  SimpleTest.waitForExplicitFinish();
    335  setTimeout(test, 0);
    336 };
    337 
    338 </script>
    339 </pre>
    340 </body>
    341 </html>